[CQOI2012]交换棋子

~~~题面~~~

题解:

其实还算是道好题

一开始很快想出了一个接近正解的建图方法,但其实是错误的,不过还是骗了70分_(:зゝ∠)_

首先我们可以观察到棋子有限,但费用多种,其实也就相当于限制了流量,找最小费用

对于初始状态的每一个1,我们连s ---> x   flow = 1  cost = 0

对于目标状态的每一个1,我们连x ---> t  flow = 1 cost = 0

对于每一个方块,我们向周围八个格子连边 flow = inf , cost = 1(表示交换了一次)

然后就是比较妙难的部分了

首先我们要拆点,因为每个点有流量限制(交换次数)

我们考虑以下三种情况

1,初始状态是0, 目标状态也是0, 我们连x ----> x‘   flow = limit/2   cost = 0

  那么为什么连的是limit/2,而不是limit呢?

  我们可以观察到对于这样一个路径1 ----> x----> 3,夹在中间的x被翻转了两次,但流量却只流经了一次,

  所以流一次实际是消耗了2的限制,因此我们直接在建边的时候就建limit/2

2,初始状态是1, 目标状态是0, 我们连x ---> x‘  flow = (limit + 1) / 2  cost = 0;

  那么为什么这里又要+1呢?

  因为这里本来就有个棋子,但目标状态却没有,因此这个棋子是必须换出去的。

  但是这种交换又和上面的不同了,因为这种情况下,点x在路径中的位置是一个端点,因此是这样的x ----> 2,

  于是我们观察到这条路径上,x并没有被翻转两次,流量却和上面一样流经了一次,也就是说如果不做任何修改,

  这样虽然只翻转了一次,却还是在被当做翻转了2次对待。这显然是不合理的。

  就比如这样的情况:

    x ----> 2 其中x的limit是1,那么这个时候x上的那个棋子显然是可以转一次就转出去的,但是如果直接按偶数建就把这次机会给忽略掉了。

3,初始状态是0, 目标状态是1, 我们连x ---> x‘  flow = (limit - 1) / 2  cost = 0;

  为什么这里是-1?

  因为这里是别的棋子要进来,进来后因为是目标位置,所以就直接去t了,不会流经x ---> x‘

  但这显然也是要浪费一个机会的,因此我们在开头就减掉这个1.

总的来说就是用1 的流量表示2个翻转次数,如果流一次会导致多统计1,那我们就在开头加一个1补回来多余的消耗

如果流一次会导致统计不到需要统计的那个1,那我们就在开头-1来表示机会被消耗掉了一个

其实应该也算是人类智慧的一种体现吧,,,强行分类嘛。

这里有一个很巧妙的实现:建边的时候直接给limit加上in[i][j] - out[i][j],具体为什么想想就知道啦,不过我觉得这是个巧合emmmm

不过貌似还有另一种解法,拆3个点,中间的边这样连:

x ---> x‘   flow = limit/2 , cost = 0

x‘ ---> x‘_  flow = limit/2 + (limit % 2 ? 1 : 0)

当然这只是我的口胡,,,具体正确性有待考证

附代码(有非常详细的注释,,,当然也有一些乱七八糟的注释,,,不过其实我觉得直接看代码应该也能懂_(:зゝ∠)_)

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define inf 2139062143
  5 #define AC 6000
  6 #define ac 80000
  7 int n, m, s, all, t, ans, ansflow, cnt, tmp;
  8 int date[ac], Head[AC], Next[ac], haveflow[ac], cost[ac], tot = 1;
  9 int dis[AC], disflow[AC], last[AC], in[30][30], out[30][30];
 10 int a[11] = {-1, 1, 0, 0, -1, -1, 1, 1}, b[11] = {0, 0, -1, 1, 1, -1, 1, -1};
 11 bool z[AC];
 12 deque<int> q;
 13 char ss[30][30];
 14 /*因为原来有,后来没有的格子必须要有一次是经过一次翻转然后出去的,因此对于这种,应该要加1的限制,
 15 而原来没有,后来有的格子,必须要有一次是经过一次翻转得到一个棋子的,因此对于这种,应该要-1的限制,
 16 这两种之所以不同就是因为原来有,现在没有的格子是要出去棋子,这种情况下肯定会消耗一个流量,且不会有多消耗的风险,
 17 而原来没有,后来有的格子,因为是从别的格子进来的,因此无法分辨这到底是要停下的流量,还是要出去的流量,
 18 而且完全可能本来是到这里停下的流量跑了出去。这时如果还保留这一个流量,就可能会造成一个点可以翻转两次,
 19 但别的棋子进来那次本来就去掉了一次了,却没有在这上面体现出来,因为流量到了这个点就直接去t了,
 20 根本不会被计入x ---> x‘的管道中。
 21 简单来说就是第一种肯定且仅会产生1的流量,而且这个流量将被计入x ---> x‘中,因此我们就要多分配1的限制去保证奇数时也可以生效。
 22 而第二种肯定且仅会产生1的流量,但这个流量将不会被计入x ---> x‘中,但是实际上这个边应当要统计到它,因为它也消耗了一次翻转限制。
 23 所以就要人为的减去这个限制,以防止偶数时这次流入打破了偶数的条件,却依然按照偶数来跑*/
 24 inline void add(int f, int w, int S, int C)
 25 {
 26     date[++tot] = w, Next[tot] = Head[f], haveflow[tot] = S, cost[tot] = C, Head[f] = tot;
 27     date[++tot] = f, Next[tot] = Head[w], cost[tot] = -C, Head[w] = tot;
 28 //    printf("%d ---> %d %d %d\n", f, w, S, C);
 29 }
 30
 31 inline int id(int x, int y)
 32 {
 33     return (x - 1) * m + y;
 34 }
 35 //error!!!流进来一次,流出去一次,一共消耗了两个流量!
 36 //所以一共块进来最多limit/2次,出去最多limit/2 + 1(单数的话)次(因为不用流进来的消耗)
 37 //因此要拆成3个点。,。。。
 38 void pre()
 39 {
 40     int x;
 41     scanf("%d%d", &n, &m);
 42     all = n * m;
 43     s = all * 2 + 1, t = s + 1;
 44     for(R i = 1; i <= n; i++)
 45     {
 46         scanf("%s", ss[i] + 1);
 47         for(R j = 1; j <= m; j++)
 48         {
 49             x = id(i, j);
 50             in[i][j] = ss[i][j] - ‘0‘;
 51             if(ss[i][j] - ‘0‘ > 0) add(s, x, 1, 0), ++tmp;
 52             for(R k = 0; k <= 7; k++)
 53                 if(i + a[k] > 0 && i + a[k] <= n && j + b[k] > 0 && j + b[k] <= m) //8个格子都要连
 54                     add(x + all, id(i + a[k], j + b[k]), inf, 1);//1 ---> 2 ---> 3这样的路径实际上只有两次翻转,而中间的点将失去两次机会
 55         }//因此只需要在1 ---> 2   2 ---> 3这样的路径中记录cost表示一次翻转就可以了,中间流量限制为limit/2即可(因为失去了两次机会)
 56     }
 57     for(R i = 1; i <= n; i++)
 58     {
 59         scanf("%s", ss[i] + 1);
 60         for(R j = 1; j <= m; j++)
 61         {
 62             x = id(i, j);
 63             out[i][j] = ss[i][j] - ‘0‘;
 64             if(ss[i][j] - ‘0‘ > 0)
 65                 add(x, t, 1, 0), ++cnt;
 66                 //只有原来有,现在没有,也就是要强制移走的时候,才应该给一次机会,多给一个流量让它流走
 67             //因为只有这个时候才会出现一条只有两个点的路径,即整条路径上的点都只消耗一的流量,所有点都是端点
 68         }//因为当需要在此格停下的时候,不用穿过去实现再一次翻转,从而浪费一些流量,因此连向t的应当是原来的点,而不是拆出来的点
 69     }
 70     if(cnt != tmp)
 71     {
 72         printf("-1\n");
 73         exit(0);
 74     }
 75     for(R i = 1; i <= n; i++)
 76     {
 77         scanf("%s", ss[i] + 1);
 78         for(R j = 1; j <= m; j++)
 79         {
 80             x = id(i, j);
 81                tmp = ss[i][j] - ‘0‘;
 82                tmp += in[i][j] - out[i][j];//通过观察可以发现这样的关系所需要的改动刚好就是in[i][j] - out[i][j]的结果
 83             if(tmp / 2 > 0) add(x, x + all, tmp / 2, 0);
 84         }
 85     }
 86 }
 87
 88 void aru()
 89 {
 90     int x = t;
 91  //   printf("%d ", t);
 92     while(x != s)
 93     {
 94         haveflow[last[x]] -= disflow[t];
 95         haveflow[last[x] ^ 1] += disflow[t];
 96         x = date[last[x] ^ 1];
 97   //      printf("<--- %d ",x);
 98     }
 99   //  printf("  cost = %d\n", dis[t]);
100     ans += disflow[t] * dis[t];
101     ansflow += disflow[t];
102 }
103
104 bool spfa()
105 {
106     int x, now;
107     disflow[s] = inf, dis[s] = 0;
108     q.push_front(s), z[s] = true;
109     while(!q.empty())
110     {
111         x = q.front();
112         q.pop_front();
113         z[x] = false;
114         for(R i = Head[x]; i ; i = Next[i])
115         {
116             now = date[i];
117             if(haveflow[i] && dis[now] > dis[x] + cost[i])
118             {
119                 dis[now] = dis[x] + cost[i];
120                 disflow[now] = min(disflow[x], haveflow[i]);
121                 last[now] = i;
122                 if(!z[now] && now != t)//error!!!now不能等于t
123                 {
124                     z[now] = true;
125                     if(!q.empty() && dis[now] < dis[q.front()]) q.push_front(now);
126                     else q.push_back(now);
127                 }
128             }
129         }
130     }
131     if(dis[t] != inf) aru();
132 //    printf("!!!%d\n", ans);
133     return dis[t] != inf;
134 }
135
136 void work()
137 {
138     memset(dis, 127, sizeof(dis));
139     while(spfa())    memset(dis, 127, sizeof(dis));
140     if(ansflow >= cnt) printf("%d\n", ans);
141     else printf("-1\n");
142 }
143
144 int main()
145 {
146     freopen("in.in","r",stdin);
147     pre();
148     work();
149     fclose(stdin);
150     return 0;
151 }

原文地址:https://www.cnblogs.com/ww3113306/p/9218191.html

时间: 2024-10-07 00:18:52

[CQOI2012]交换棋子的相关文章

BZOJ2668: [cqoi2012]交换棋子

题解: 可以戳这里:http://www.cnblogs.com/zig-zag/archive/2013/04/21/3033485.html 其实自己yy一下就知道这样建图的正确性了. 感觉太神奇,居然还能拆成3个点 orzzzzzzzzzzzzzzzzzzzzzzzzz 跪跪跪跪跪跪跪跪 代码: 1 #include<cstdio> 2 3 #include<cstdlib> 4 5 #include<cmath> 6 7 #include<cstring&

[CQOI2012]交换棋子(最小费用最大流)

[CQOI2012]交换棋子(luogu) Description 题目描述 有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子, 最终达到目标状态.要求第i行第j列的格子只能参与mi,j次交换. 输入格式 第一行包含两个整数n,m(1<=n, m<=20).以下n行为初始状态,每行为一个包含m个字符的01串, 其中0表示黑色棋子,1表示白色棋子.以下n行为目标状态,格式同初始状态. 以下n行每行为一个包含m个0~9数字的字符串,表示每个格子参与交换的次

[bzoj2668] [洛谷P3159] [cqoi2012] 交换棋子

Description 有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态.要求第i行第j列的格子只能参与mi,j次交换. Input 第一行包含两个整数n,m(1<=n, m<=20).以下n行为初始状态,每行为一个包含m个字符的01串,其中0表示黑色棋子,1表示白色棋子.以下n行为目标状态,格式同初始状态.以下n行每行为一个包含m个0~9数字的字符串,表示每个格子参与交换的次数上限. Output 输出仅一行,为最小交换总次数.如果

BZOJ 2668 [cqoi2012]交换棋子 | 最小费用最大流

传送门 BZOJ 2668 题解 同时分别限制流入和流出次数,所以把一个点拆成三个:入点in(x).中间点mi(x).出点ou(x). 如果一个格子x在初始状态是黑点,则连(S, mi(x), 1, 0) 如果x在目标状态是黑点,则连(mi(x), T, 1, 0) 设x的交换次数限制是w 如果x在两种状态中颜色相同,则连(in(x), mi(x), w / 2, 0), (mi(x), ou(x), w / 2, 0) 如果x只在初始状态为黑色,则连(in(x), mi(x), w / 2,

洛谷P3159 [CQOI2012]交换棋子

巧妙的拆点方式,首先把1看成黑点,0看成空的,几次交换就可以看成一条路径 1)从容量上看,这条路径为1-2-2-2-2-2----2-1 2)从费用上看,这条路径每条边费用都是1 于是用一种巧妙的拆点方式,把一个点拆成三个,连两条边,成为一条链, 然后如果是黑点的话就由s向中间那个点连边,如果是路过的话就由一条链的尾部向另一条链的首部连边 这样就满足了上面的条件1)2) 容量的话如果是黑点出来就是(c+1)/2,进来是c/2,其他的以此类推 1 #include<cstdio> 2 #incl

【题解】CQOI2012交换棋子

感受到网络流的强大了--这道题目的关键在于: 前后颜色不变的,流入流出的次数相等:原本是黑色的最后变成了白色,流出比流入次数多1:原本是白色最后变成黑色,流入比流出次数多一.所以我们将每一点拆成3个点,分别代表流入点,原点与流出点.最开始为黑色的点与源点连流量为1,费用为0的边,最后为黑色的点与汇点连流量为1,费用为0的边. #include<bits/stdc++.h> using namespace std; #define maxn 300 #define maxm 8000 #defi

p3159 [CQOI2012]交换棋子

传送门 分析 https://www.luogu.org/blog/dedicatus545/solution-p3159 代码 #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<cctype> #include<cmath> #include<cstdlib>

【BZOJ-2668】交换棋子 最小费用最大流

2668: [cqoi2012]交换棋子 Time Limit: 3 Sec  Memory Limit: 128 MBSubmit: 1055  Solved: 388[Submit][Status][Discuss] Description 有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子(相邻是指有公共边或公共顶点)中的棋子,最终达到目标状态.要求第i行第j列的格子只能参与mi,j次交换. Input 第一行包含两个整数n,m(1<=n, m<=20).以下n行为初始状态,每行为一个

BZOJ 2668 交换棋子(费用流)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2668 题意:有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子中的棋子,最终达到目标状态.要求第i行第j列的格子只能参与m[i,j]次交换. 思路: 我们将1看做要移动的数字,将0看做空白.那么若1在始末状态个数不同则无解:如某个格子始末状态均有1则这个格子的1对结果无影响,可以将其都置为0.将每个格子拆为为个点p0,p1,p2: (1)若格子初始为1,则连边:<s,p0,1,0>