F - Zebraness (Caddi Programming Contest 2021(AtCoder Beginner Contest 193))

题目来源

https://atcoder.jp/contests/abc193/tasks/abc193_f

题意分析

  给出一个二维的表格,每一个格子上有一个字符表示当前格子上面的颜色。‘W’表示白色,‘B’表示黑色,‘?’表示还没有上色。求当给所有未上色的格子上色之后,白色与黑色格子相邻的边最大的数量。

思路分析

  看到黑白两种颜色之后,比较容易想到的是二分图,进而想到网络流。而最后可以将问题化为最小割的问题,这也是官方题解。
  可以想到的是,将所有黑色的格子和白色的格子分成二分图的两边,还没有染色的格子就放在中间,将相邻的格子都进行连边即可。这样去跑网络流,我们最后得到的是这张图那类边最小的数量。在官方题解中,将i+j分成奇偶两个部分,并将其中一个部分的黑白颠倒(即黑变白,白变黑),然后再跑网络流,得出的结果就是最小割。表格总共有2 * n * (n-1)的边,减去之后就是最终的答案。
  回想这个过程,可以想清楚的是将奇偶分开是因为只有奇偶的格子之间连边,而进行颜色颠倒的一个原因是,当我们使用二分图将黑色和白色的格子分开的时候,黑色边与黑色边的连边并不能形成一条有效的增广路,而我们的目的是为了将这些相同相邻颜色的格子之间的边去掉,所以当颠倒之后,就很自然的分到了两边去。

  最后最终要的是,问题是如何转换成最小割问题的。对于这样的问题,当我们想到分成二分图的时候,我们肯定会想到一个阶段,即这个最大值的数量在计数的时候到底是正着计数还是倒着计数(即去掉不符合条件的边)。而在本题之中,如果选择正着计数的话,那就是相邻的两个格子颜色不同时会对我们的答案有贡献,但在这种条件下直接跑网络流会出错。最后究其原因,个人以为是在某些无法流通的边下,仍然存在可以被记入答案的贡献边。最简单的例子是一个只含有黑色和未涂色的二维表格。整张网络流是没有流量的,但是当我们将未涂色的格子填上白色之后其实就可以跑通了。(但其实后来看了一些人的提交之后,发现正向的计数是可以实现的)。而之后就考虑反向的计数,发现的问题和解决方案也就如上文所述。

code

  1 #include <bits/stdc++.h>
  2 
  3 #define ll long long
  4 #define INF 0x3f3f3f3f
  5 using namespace std;
  6 const int maxn = 3e3 + 7;
  7 
  8 struct edge{
  9     int u, v, nxt, f;
 10 };
 11 
 12 char ch[maxn][maxn];
 13 edge e[maxn * maxn];
 14 int head[maxn * maxn], tot, ver[maxn * maxn], nxt[maxn * maxn], wi[maxn * maxn];
 15 int dep[maxn * maxn], cur[maxn * maxn];
 16 int S, T;
 17 int dc[4][2] = {{1, 0}, {0, 1}};
 18 
 19 
 20 void Init(){
 21     memset(head, -1, sizeof (head));
 22     memset(cur, 0, sizeof (cur));
 23     tot = 0;
 24 }
 25 
 26 void adde(int u, int v, int f){
 27     ver[tot] = v; wi[tot] = f; nxt[tot] = head[u]; head[u] = tot++;
 28     ver[tot] = u; wi[tot] = 0; nxt[tot] = head[v]; head[v] = tot++;
 29 }
 30 
 31 bool bfs(){
 32     queue <int> q;
 33     memset(dep, -1, sizeof (dep));
 34     dep[S] = 0; q.push(S);
 35 
 36     while (!q.empty()){
 37         int h = q.front(); q.pop();
 38 
 39         for (int i=head[h]; ~i; i=nxt[i]){
 40             int v = ver[i];
 41             if (dep[v] == -1 && wi[i] > 0){
 42                 dep[v] = dep[h] + 1;
 43                 q.push(v);
 44                 if (v == T) return true;
 45             }
 46         }
 47     }
 48     return false;
 49 }
 50 
 51 int dfs(int x, int mw){
 52     if (x == T || !mw) return mw;
 53     int sum = 0;
 54     for (int i=cur[x]; ~i; i = nxt[i]){
 55         cur[x] = i;
 56         int v = ver[i];
 57         if (dep[v] == dep[x] + 1 && wi[i] > 0){
 58             int flow = dfs(v, min(wi[i], mw));
 59             if (flow){
 60                 sum += flow;
 61                 mw -= flow;
 62                 wi[i] -= flow;
 63                 wi[i^1] += flow;
 64                 if (!mw) break;
 65             }
 66         }
 67     }
 68     if (!sum) dep[x] = -1;
 69     return sum;
 70 }
 71 
 72 int dinic(){
 73     int ans = 0;
 74     while(bfs()){
 75         for (int i=S; i<=T; i++) cur[i] = head[i];
 76         ans += dfs(S, INF);
 77     }
 78     return ans;
 79 }
 80 
 81 int main(){
 82 //    cout << ('B' ^ 'W') << endl;
 83     Init();
 84 
 85     int n; scanf("%d", &n);
 86 //    cout << 111 << endl;
 87     for (int i=1; i<=n; i++) scanf("%s", ch[i] + 1);
 88     S = 0; T = n * n + 1;
 89     for (int i=1; i<=n; i++){
 90         for (int j=1; j<=n; j++){
 91             if ((i ^ j) & 1 && ch[i][j] != '?') ch[i][j] ^= ('B' ^ 'W');
 92         }
 93     }
 94 
 95     for (int i=1; i<=n; i++){
 96         for (int j=1; j<=n; j++){
 97             if (ch[i][j] == 'B') adde(S, (i-1)*n+j, INF);
 98             if (ch[i][j] == 'W') adde((i-1)*n+j, T, INF);
 99             for (int k=0; k<2; k++){
100                 int x = i + dc[k][0], y = j + dc[k][1];
101                 if (x < 1 || y < 1 || x > n || y > n) continue;
102                 adde((i-1)*n+j, (x-1)*n+y, 1);
103                 adde((x-1)*n+y, (i-1)*n+j, 1);
104             }
105         }
106     }
107 
108     int ans = dinic();
109     printf("%d\n", 2 * (n-1) * n - ans);
110     return 0;
111 }

 

posted @ 2021-03-01 21:22  Rain_island  阅读(129)  评论(0编辑  收藏  举报
Title