比赛场上很容易想到是费用流,但是没有想到建图方法qwq,太弱了。
这里直接贴官方题解:
费用流。离散化坐标,每行用一个点表示,每列也用一个点表示。表示第i-1行的点向表示第i行的点连边,容量为第i行及以后能拿的棋子数的上限,费用为0,同理表示相邻列的点两两连边。若第i行第j列上有棋子,则表示第i行的点向表示第j列的点连边,容量为1,费用为该棋子的价值。可以定义源点表示第0行,汇点表示第0列,源点到汇点的最大费用流即为答案。
就是按照题解的建图方法,还有一些小细节:先要排序排除无用限制来减少限制边数,不然会超时。我用的办法是,按限制从小到大排序,大限制当且仅当它的行数小于小限制行数时才有用。列同理。这里想不明白的建议画图细细想。然后就是连边来表示限制条件:行的话就是(i-1)->i行连边,列的话就是i->(i-1)列连边,这是因为0行是源点0列是汇点所致的,行点要靠它的入边来限制流量,列点要靠出边来限制流量。
细节详见代码及注释:
#include<bits/stdc++.h> using namespace std; const int N=5000+10; const int M=100000+10; const int INF=0x3f3f3f3f; int n,m,r,c,s,t,maxflow,mincost; int nx,ny,x[N],y[N],xx[N],yy[N],bx[N],by[N]; struct edge{ int nxt,to,cap,cost; }edges[M<<1]; int cnt=1,head[N],pre[N]; struct dat{ int t,l; } R[M],C[M]; bool cmp(dat a,dat b) { return a.l<b.l || a.l==b.l && a.t<b.t; } void add_edge(int x,int y,int z,int c) { edges[++cnt].nxt=head[x]; edges[cnt].to=y; edges[cnt].cap=z; edges[cnt].cost=c; head[x]=cnt; } queue<int> q; int dis[N],lim[N]; bool inq[N]; bool spfa(int s,int t) { while (!q.empty()) q.pop(); memset(dis,0x3f,sizeof(dis)); memset(inq,0,sizeof(inq)); dis[s]=0; inq[s]=1; lim[s]=INF; q.push(s); while (!q.empty()) { int x=q.front(); q.pop(); for (int i=head[x];i;i=edges[i].nxt) { edge e=edges[i]; if (e.cap && dis[x]+e.cost<dis[e.to]) { dis[e.to]=dis[x]+e.cost; pre[e.to]=i; //即e.to这个点是从i这条边来的 lim[e.to]=min(lim[x],e.cap); if (!inq[e.to]) { q.push(e.to); inq[e.to]=1; } } } inq[x]=0; } return !(dis[t]==INF); } void MCMF() { maxflow=0; mincost=0; while (spfa(s,t)) { int now=t; maxflow+=lim[t]; mincost+=lim[t]*dis[t]; while (now!=s) { edges[pre[now]].cap-=lim[t]; edges[pre[now]^1].cap+=lim[t]; now=edges[pre[now]^1].to; } } } int main() { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]),bx[i]=x[i],by[i]=y[i]; nx=ny=n; scanf("%d",&m); char opt[3]; for (int i=1;i<=m;i++) { int tx,ty; scanf("%s%d%d",opt,&tx,&ty); if (opt[0]=='R') R[++r]=(dat){tx,ty}; if (opt[0]=='C') C[++c]=(dat){tx,ty}; } int tmp=0; sort(R+1,R+r+1,cmp); for (int i=1;i<=r;i++) //排除行无用限制 if (tmp==0 || R[i].t<R[tmp].t) R[++tmp]=R[i],bx[++nx]=R[i].t; r=tmp; tmp=0; sort(C+1,C+c+1,cmp); for (int i=1;i<=c;i++) //排除列无用限制 if (tmp==0 || C[i].t<C[tmp].t) C[++tmp]=C[i],by[++ny]=C[i].t; c=tmp; sort(bx+1,bx+nx+1); nx=unique(bx+1,bx+nx+1)-(bx+1); //离散化 sort(by+1,by+ny+1); ny=unique(by+1,by+ny+1)-(by+1); //离散化 for (int i=1;i<=n;i++) { int tx=lower_bound(bx+1,bx+nx+1,x[i])-bx; int ty=lower_bound(by+1,by+ny+1,y[i])-by; add_edge(tx,nx+1+ty,1,-i); add_edge(nx+1+ty,tx,0,i); //棋子连边 } memset(xx,0x3f,sizeof(xx)); memset(yy,0x3f,sizeof(yy)); for (int i=1;i<=r;i++) { int tx=lower_bound(bx+1,bx+nx+1,R[i].t)-bx; xx[tx]=min(xx[tx],R[i].l); //先求好限制条件,xx[i]代表i行及以后的最小限制 } for (int i=1;i<=c;i++) { int ty=lower_bound(by+1,by+ny+1,C[i].t)-by; yy[ty]=min(yy[ty],C[i].l); //列同行同理 } //这里是关键:行i-1->i为了限制i的出流,列i->i-1为了限制i的出流 for (int i=1;i<=nx;i++) add_edge(i-1,i,xx[i],0),add_edge(i,i-1,0,0); for (int i=1;i<=ny;i++) add_edge(nx+1+i,nx+1+i-1,yy[i],0),add_edge(nx+1+i-1,nx+1+i,0,0); s=0; t=nx+1; MCMF(); cout<<-mincost<<endl; return 0; }
来源:https://www.cnblogs.com/clno1/p/10957772.html