交换棋子[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:
4
格子的交换其实可以看做黑棋的移动。所以我们可以考虑拆点做费用流。
我一开始的思路:
- 把每个点拆成两个点xi,yi;
- xi->yi连容量为该点交换次数,费用为0的边;
- yi->xj连容量为INF,费用为1的边;(i与j有公共边或公共点)
- s->xi连容量为1,费用为0的边;(格子i开始时为黑棋)
- yi->t连容量为1,费用为0的边;(格子i结束时为黑棋)
可是这样建图是不对的。考虑一条移动的路径,起点和终点翻转了一次,路径上的其余点其实反转了两次,那我们怎么解决这个问题呢?
我们先不考虑起点和终点,那么路径上的格子其实每经过一个棋子就要被翻转两次,所以我们让通过次数除以2就可以了。再考虑起点和终点,如果一个格子既是起点又是终点,显然可以不用管它,当做这里没有棋子处理即可。对于一个是起点或终点的格子,我们可以先给他留出一个初始通过的交换次数,即让以该格子为起点的棋子离开,然后剩下的就按照这个格子是路径上的点处理即可,即容量为(w-1)/2+1。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<queue> 6 #include<bitset> 7 #define dx(i,j) ((i-1)*m+j) 8 #define dy(i,j) ((i-1)*m+j+400) 9 using namespace std ; 10 const int INF = 0x7ffffff ; 11 const int N = 20 + 2 ; 12 const int FN = 800 + 10 ; 13 const int M = 1e6 + 10 ; 14 15 const int cx[] = {-1,-1,-1,0,0,1,1,1} ; 16 const int cy[] = {1,0,-1,1,-1,1,0,-1} ; 17 18 inline int read() { 19 int k = 0, f = 1 ; char c = getchar() ; 20 for( ; !isdigit(c) ; c = getchar()) 21 if(c == '-') f = -1 ; 22 for( ; isdigit(c) ; c = getchar()) 23 k = k*10 + c-'0' ; 24 return k*f ; 25 } 26 struct Edge { 27 int to, next, flow, cost ; 28 }e[M] ; 29 int n, m, s, t, ansf, ansc ; int head[FN], dis[FN] ; bool hh[N][N], gg[N][N], vis[FN] ; 30 inline void add_edge(int x,int y,int ff,int cc) { 31 static int cnt = 1 ; 32 e[++cnt].to = y, e[cnt].next = head[x], head[x] = cnt, e[cnt].flow = ff, e[cnt].cost = cc ; 33 e[++cnt].to = x, e[cnt].next = head[y], head[y] = cnt, e[cnt].flow = 0, e[cnt].cost = -cc ; 34 } 35 36 inline bool spfa() { 37 for(int i=1;i<=t;i++) dis[i] = INF ; dis[s] = 0 ; 38 deque<int>q ; q.push_back(s) ; bitset<FN>inq ; inq[s] = 1 ; 39 while(!q.empty()) { 40 int x = q.front() ; q.pop_front() ; inq[x] = 0 ; 41 for(int i=head[x];i;i=e[i].next) { 42 int y = e[i].to ; if(!e[i].flow) continue ; 43 if(dis[y] > dis[x]+e[i].cost) { 44 dis[y] = dis[x]+e[i].cost ; 45 if(!inq[y]) { 46 inq[y] = 1 ; 47 if(!q.empty() && dis[y] < dis[q.front()]) q.push_front(y) ; 48 else q.push_back(y) ; 49 } 50 } 51 } 52 } 53 return dis[t] < INF ; 54 } 55 int FFdfs(int x,int minflow) { 56 vis[x] = 1 ; 57 if(x == t || !minflow) return minflow ; 58 int fflow = 0 ; 59 for(int i=head[x];i;i=e[i].next) { 60 int y = e[i].to ; if(!e[i].flow || vis[y] || dis[y] != dis[x]+e[i].cost) continue ; 61 int temp = FFdfs(y,min(minflow,e[i].flow)) ; 62 fflow += temp, minflow -= temp ; 63 e[i].flow -= temp, e[i^1].flow += temp ; 64 ansc += temp*e[i].cost ; 65 if(!minflow) break ; 66 } 67 return fflow ; 68 } 69 70 int main() { 71 n = read(), m = read() ; s = 801, t = s+1 ; int tot = 0, tot1 = 0 ; 72 for(int i=1;i<=n;i++) 73 for(int j=1;j<=m;j++) { 74 char cc ; cin>>cc ; 75 if(cc == '1') { 76 hh[i][j] = 1, tot1++ ; 77 } 78 } 79 for(int i=1;i<=n;i++) 80 for(int j=1;j<=m;j++) { 81 char cc ; cin>>cc ; 82 if(cc == '1') { 83 gg[i][j] = 1, tot++ ; 84 } 85 } 86 while(tot != tot1) { 87 printf("-1") ; return 0 ; 88 } tot = 0 ; 89 for(int i=1;i<=n;i++) 90 for(int j=1;j<=m;j++) { 91 if(hh[i][j] && !gg[i][j]) add_edge(s,dx(i,j),1,0), tot++ ; 92 else if(!hh[i][j] && gg[i][j]) add_edge(dy(i,j),t,1,0) ; 93 } 94 for(int i=1;i<=n;i++) 95 for(int j=1;j<=m;j++) { 96 char cc ; cin>>cc ; int x = cc-'0' ; 97 add_edge(dx(i,j),dy(i,j),x>>1,0) ; 98 if(hh[i][j] != gg[i][j] && x&1) add_edge(dx(i,j),dy(i,j),1,0) ; 99 for(int k=0;k<8;k++) { 100 int xx = i+cx[k], yy = j+cy[k] ; if(!xx || !yy || xx > n || yy > m) continue ; 101 add_edge(dy(i,j),dx(xx,yy),INF,1) ; 102 } 103 } 104 while(spfa()) { 105 vis[t] = 1 ; 106 while(vis[t]) { 107 memset(vis,0,sizeof(vis)) ; 108 ansf += FFdfs(s,INF) ; 109 } 110 } 111 if(ansf < tot) { 112 printf("-1") ; return 0 ; 113 } 114 printf("%d",ansc) ; 115 return 0 ; 116 }