【BZOJ-2668】交换棋子 最小费用最大流
2668: [cqoi2012]交换棋子
Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 1055 Solved: 388
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
110
000
001
000
110
100
222
222
222
Sample Output
HINT
Source
Solution
这是一道建图比较有趣的费用流.
想到用费用流比较容易,很显然,连法必然是S连向原图中的黑点,新图中的黑点连向T,问题在于中间的连边,如何利用容量限制两个点。
自己一开始立马想到拆点,但是是很naive的一拆二,限制容量,但这样并不可以。
实际上这个题需要一个点拆成3个点,我们把点$x$拆成$x_{1},x_{2},x_{3}$,并连出$x_{1}-->x_{2}-->x_{3}$
其中$x_{1}-->x_{2}$表示$x$这个点最多流入的流量,$x_{2}-->x_{3}$表示$x$这个点最多流出的流量。
对于一个点$x$,如果只是原图的黑点,那么限制$<x_{1},x_{2}>:cap=\frac{use[x]}{2};cost=0 ; <x_{2},x_{3}>:cap=\frac{use[x]+1}{2};cost=0$
并且连$S-->x_{2} : cap=1;cost=0$
对于一个点$x$,如果只是新图的黑点,那么限制$<x_{1},x_{2}>:cap=\frac{use[x]+1}{2};cost=0 ; <x_{2},x_{3}>:cap=\frac{use[x]}{2};cost=0$
并且连$x_{2}-->T : cap=1;cost=0$
如果一个点$x$,如果在原图和新图中都是白点或者都是黑点,那么我们限制$<x_{1},x_{2}>:cap=\frac{use[x]}{2};cost=0 ; <x_{2},x_{3}>:cap=\frac{use[x]}{2};cost=0$
对于图中的八连通格两两$x ; y$,连边$x_{3}-->y_{1} :cap=INF ; cost=1$流经此边,表示交换一次。
上述的限制方法,实际上是对网格的每个位置的流量变化进行讨论得到的,因为是一个最值情况,所以很多流入流出限制都无法完全流满。
Code
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> using namespace std; #define MAXM 100010 #define MAXN 2000 int N,M,used[50][50]; char st[50][50],ed[50][50],us[50][50]; struct EdgeNode{int next,to,cap,cost;}edge[MAXM]; int cnt=1,head[MAXN]; inline void AddEdge(int u,int v,int w,int c) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v; edge[cnt].cap=w; edge[cnt].cost=c;} inline void InsertEdge(int u,int v,int w,int c) {AddEdge(u,v,w,c); AddEdge(v,u,0,-c);} int dis[MAXN],S,T,Cost,Flow; bool mark[MAXN]; #define INF 0x7fffffff inline bool SPFA() { memset(mark,0,sizeof(mark)); for (int i=S; i<=T; i++) dis[i]=INF; queue<int>q; q.push(S); dis[S]=0; mark[S]=1; while (!q.empty()) { int now=q.front(); q.pop(); mark[now]=0; for (int i=head[now]; i; i=edge[i].next) if (edge[i].cap && dis[edge[i].to]>dis[now]+edge[i].cost) { dis[edge[i].to]=dis[now]+edge[i].cost; if (!mark[edge[i].to]) q.push(edge[i].to),mark[edge[i].to]=1; } } return dis[T]!=INF; } inline int DFS(int loc,int low) { mark[loc]=1; if (loc==T) return low; int used=0,w; for (int i=head[loc]; i; i=edge[i].next) if (edge[i].cap && !mark[edge[i].to] && dis[edge[i].to]==dis[loc]+edge[i].cost) { w=DFS(edge[i].to,min(edge[i].cap,low-used)); edge[i].cap-=w; edge[i^1].cap+=w; used+=w; Cost+=w*edge[i].cost; if (low==used) return used; } return used; } inline int zkw() { int re=0; while (SPFA()) { mark[T]=1; while (mark[T]) memset(mark,0,sizeof(mark)),re+=DFS(S,INF); } return re; } int id[50][50][4],ID,black,white; int dx[10]={-1,-1,-1,0,0,1,1,1},dy[10]={-1,0,1,-1,1,-1,0,1}; inline bool check(int x,int y) {return x>=1 && x<=N && y>=1 && y<=M;} void BuildGraph() { for (int i=1; i<=N; i++) for (int j=1; j<=M; j++) for (int k=1; k<=3; k++) id[i][j][k]=++ID; S=0,T=ID+1; for (int i=1; i<=N; i++) for (int j=1; j<=M; j++) { if (st[i][j]=='0' && ed[i][j]=='1') black++, InsertEdge(S,id[i][j][2],1,0), InsertEdge(id[i][j][1],id[i][j][2],used[i][j]/2,0), InsertEdge(id[i][j][2],id[i][j][3],(used[i][j]+1)/2,0); if (st[i][j]=='1' && ed[i][j]=='0') white++, InsertEdge(id[i][j][2],T,1,0), InsertEdge(id[i][j][1],id[i][j][2],(used[i][j]+1)/2,0), InsertEdge(id[i][j][2],id[i][j][3],used[i][j]/2,0); if (st[i][j]==ed[i][j]) InsertEdge(id[i][j][1],id[i][j][2],used[i][j]/2,0), InsertEdge(id[i][j][2],id[i][j][3],used[i][j]/2,0); } for (int i=1; i<=N; i++) for (int j=1; j<=M; j++) for (int x,y,k=0; k<8; k++) { x=i+dx[k],y=j+dy[k]; if (check(x,y)) InsertEdge(id[i][j][3],id[x][y][1],INF,1); } } int main() { scanf("%d%d",&N,&M); for (int i=1; i<=N; i++) scanf("%s",st[i]+1); for (int i=1; i<=N; i++) scanf("%s",ed[i]+1); for (int i=1; i<=N; i++) scanf("%s",us[i]+1); for (int i=1; i<=N; i++) for (int j=1; j<=M; j++) used[i][j]=us[i][j]-'0'; BuildGraph(); Flow=zkw(); printf("%d\n",black==white? (Flow==black? Cost : -1) : -1); return 0; }
脑残RE了好几次...