P3159 [CQOI2012]交换棋子
题目描述
有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态。要求第i行第j列的格子只能参与mi,j次交换。
输入格式
第一行包含两个整数n,m(1<=n, m<=20)。以下n行为初始状态,每行为一个包含m个字符的01串,其中0表示黑色棋子,1表示白色棋子。以下n行为目标状态,格式同初始状态。以下n行每行为一个包含m个0~9数字的字符串,表示每个格子参与交换的次数上限。
输出格式
输出仅一行,为最小交换总次数。如果无解,输出-1。
输入输出样例
输入 #1
3 3 110 000 001 000 110 100 222 222 222
输出 #1
4
思路
对于棋盘的初始状态和最终状态可以看做二分图的两组点,而棋子的上下左右移动可以视作边的。交换黑白点可以视作讲黑子进行移动,每次费用为1,因此我们可以进行建图,但这题十分的特殊,因为它限制了每个格子的交换次数,并且用平常的拆点,我们无法表示它是否停留在改点,因此我们将1个点拆为3个点。分别为(left,now,right),每个点从left进入,停留在now上,从right出去。但对于两条边,它所限制的交换次数该如何划分又是一个问题。我们不妨把它视作黑点移动经过的次数。
设limt表示限制数。
对于初始和结果相同的点,边的为(l,now,limt/2,1),(now,r,limt/2,1)。
对于初始为黑,结果为白的情况边为(l,now,limt/2,1),(now,r,(limt+1)/2,1)。
对于初始为白,结果为黑的情况则与之相反。
原理:如果开始为黑,结果为白,则黑点一定要移动出去,则若limt为奇数时,多余的1应该加入出边。若为偶数,则加1不会对其有影响。另一种情况与之相反。
最后将源点和汇点连入图中,跑最大流最小费用即可。
代码
#include<bits/stdc++.h> #define N 10700 #define M 107000 #define inf 1<<29 #define cnt (n*m*3) #define left(i,j) ((i-1)*m+j) #define now(i,j) (((i-1)*m+j)+n*m) #define right(i,j) (((i-1)*m+j)+(n*m<<1)) using namespace std; struct node{ int y,z,p,next; }e[M*2]; int tot=1,head[N],maxflow=0,ans=0; int n,m,s,t; void add(int x,int y,int z,int p){ e[++tot].y=y;e[tot].z=z;e[tot].p=p;e[tot].next=head[x];head[x]=tot; e[++tot].y=x;e[tot].z=0;e[tot].p=-p;e[tot].next=head[y];head[y]=tot; } int incf[N],v[N],pre[N],d[N]; bool spfa(){ queue<int> q; memset(d,0x3f,sizeof(d));// 0xcf memset(v,0,sizeof(v)); q.push(s);d[s]=0;v[s]=1; incf[s]=inf; while(q.size()){ int x=q.front();v[x]=0;q.pop(); for(int i=head[x];i;i=e[i].next){ int y=e[i].y,z=e[i].z; if(!z) continue; if(d[y]>d[x]+e[i].p){//d[y]<d[x]+e[i].p d[y]=d[x]+e[i].p; incf[y]=min(incf[x],z); pre[y]=i; if(!v[y]) v[y]=1,q.push(y); } } } if(d[t]==0x3f3f3f3f) return false;//0xcfcfcfcf return true; } void update(){ int x=t; while(x!=s){ int i=pre[x]; e[i].z-=incf[t]; e[i^1].z+=incf[t]; x=e[i^1].y; } maxflow+=incf[t]; ans+=d[t]*incf[t]; } int x1[N][N],x2[N][N],z[N][N]; int dx[9]={0,-1,-1,-1,0,0,1,1,1}; int dy[9]={0,-1,0,1,-1,1,-1,0,1}; int main() { int t1=0,t2=0,ti,tj,k;char c[30]; cin>>n>>m;s=0;t=cnt+1; for(int i=1;i<=n;i++){ scanf("%s",c); for(int j=1;j<=m;j++){ if(c[j-1]=='1'){ t1++;add(s,now(i,j),1,0); x1[i][j]=1; } } } for(int i=1;i<=n;i++){ scanf("%s",c); for(int j=1;j<=m;j++){ if(c[j-1]=='1'){ t2++;add(now(i,j),t,1,0); x2[i][j]=1; } } } if(t1!=t2){ cout<<"-1"<<endl;return 0; } for(int i=1;i<=n;i++){ scanf("%s",c); for(int j=1;j<=m;j++){ t2=c[j-1]-'0'; if(x1[i][j]==x2[i][j]) add(left(i,j),now(i,j),t2/2,0),add(now(i,j),right(i,j),t2/2,0); if(x1[i][j]&&!x2[i][j]) add(left(i,j),now(i,j),t2/2,0),add(now(i,j),right(i,j),(t2+1)/2,0); if(!x1[i][j]&&x2[i][j]) add(left(i,j),now(i,j),(t2+1)/2,0),add(now(i,j),right(i,j),t2/2,0); } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ for(k=1;k<=8;k++){ ti=i+dx[k];tj=j+dy[k]; if(ti<1||ti>n||tj<1||tj>m) continue; add(right(i,j),left(ti,tj),inf,1); } } } while(spfa()) update(); if(maxflow==t1) cout<<ans<<endl; else cout<<"-1"<<endl; return 0; }