题解 P5038 【[SCOI2012]奇怪的游戏】

· · 题解

二分 \times 网络流

一开始看到题目脑子一片空白。。。QAQ

仔细观察题目:

选择两个相邻的格子

黑白染色!QwQ

并使这两个数都加上1

二分图!QwQ

棋盘上的数都变成同一个数

最大流!QwQ

最少多少次

二分!QwQ

好了这题解法已经很明朗了。。。(诶诶你刚刚说了什么来着?)

具体来说

因为这题是二维棋盘,考虑黑白染色,建立二分图。

设黑点的个数有 B 个,权值总和为 b ,白点的个数有 W 个,权值总和为 w

对于每次操作,因为是选择相邻的两个节点,所以黑点白点肯定各有一个被操作,即 bw 各加一。

设最后全部数字都变成 X ,那么就会有有 B \times X - b = W \times X - w (等于操作的次数)。

稍微化简一下: (B - W) \times X = b - w X = (b - w) \div (B - W)

这条等式成立的条件是 B \ne W ,(小学奥数:除数不能为0)

B \ne W

如果 B \ne W ,我们可以直接求出 X

然而,我们直接求出的X不一定是可行的,需要使用check判定它是否可行(见后)。

B = W

如果 B = W ,就意味着 NM 中有一个是偶数(很明显好吧qwq)。

B=W 的情况下,如果一个 X 可行,那么我们可以铺满整个棋盘,使整个棋盘的所有数都加上一,即 X+1 也可行。

同理,当 X 不可行时, X-1 也肯定不可行。

我们可以二分出 X ,同样使用check判定。

注意:我们每次 bw 各加一,而且 B = W ,最后所有数都变成 X ,因此若一开始的 b 不等于 w ,我们不管怎么操作,都不能求出一个合法的解。

check函数

设第 $i$ 个点的值为 $v[i]$ , 网络流建图,将 $S$ 向所有黑点 $i$ 连边,容量为 $x - v[i]$ ,意味着需要对这个点操作 $x - v[i]$ 次。 将所有黑点 $i$ 向其相邻的白点 $j$ 连边,容量为 $INF$ 。 将所有白点 $j$ 向 $T$ 连边,容量为 $x - v[j]$ ,意味着这个点最多接受 $x - v[j]$ 次来自黑点的操作。 设所有 $x - v[i]$ 的和为 $V$ , 在图上运行最大流,判断最大流是否等于 $V$ 即可。 若最大流等于 $V$ ,那么意味着图上所有边都流满,即 $x$ 可行,返回`true`。 否则返回`false`。 ## 代码 ```cpp #include<bits/stdc++.h> using namespace std; #define int long long int INF; const int dx[] = {0, 0, -1, 1}; const int dy[] = {-1, 1, 0, 0}; struct Graph{ static const int MAXM=200000+10; static const int MAXN=10000+10; struct Edge{ int from,to,cap; int next; }node[MAXM]; int head[MAXN],top; void init(){ top=1; memset(head, 0, sizeof(head)); s = MAXN - 1; t = MAXN - 2; } void add(int u,int v,int cap){ top++; node[top].next=head[u]; node[top].from=u; node[top].to=v; node[top].cap=cap; head[u]=top; top++; node[top].next=head[v]; node[top].from=v; node[top].to=u; node[top].cap=0; head[v]=top; } int s,t; int dis[MAXN]; queue<int> Q; bool bfs(){ memset(dis,-1,sizeof(dis)); Q.push(s); dis[s]=0; while(!Q.empty()){ int u=Q.front(); Q.pop(); for(int i=head[u];i;i=node[i].next){ int v=node[i].to; if(dis[v]==-1&&node[i].cap){ dis[v]=dis[u]+1; Q.push(v); } } } return dis[t]!=-1; } int dfs(int u,int flow){ if(u==t) return flow; else{ int ret=flow; for(int i=head[u];i&&ret;i=node[i].next){ int v=node[i].to; if(dis[v]==dis[u]+1&&node[i].cap){ int k=dfs(v,min(ret,node[i].cap)); node[i].cap-=k; node[i^1].cap+=k; ret-=k; } } if(ret == flow) dis[u] = -1; return flow-ret; } } int dinic(){ int ans=0; while(bfs()) ans+=dfs(s,INF); return ans; } }G; #define POINT(X, Y) ((X) * 40 + (Y)) int T, n, m; int a[100][100]; bool check(int val) { G.init(); int sum = 0; for(int i = 1; i <= n; i ++) { for(int j = 1; j <= m; j ++) { if((i + j) % 2 == 0) { // X G.add(G.s, POINT(i, j), val - a[i][j]); sum += val - a[i][j]; for(int k = 0; k < 4; k ++) { int tx = i + dx[k]; int ty = j + dy[k]; if(tx >= 1 && tx <= n && ty >= 1 && ty <= m) { G.add(POINT(i, j), POINT(tx, ty), INF); } } } else { // Y G.add(POINT(i, j), G.t, val - a[i][j]); } } } return G.dinic() == sum; } signed main() { INF = 0x7F7F7F7F7F7F7F7F; cin>>T; while(T --) { cin>>n>>m; int max1 = 0; int s0, s1, c0, c1; s0 = s1 = c0 = c1 = 0; for(int i = 1; i <= n; i ++) { for(int j = 1; j <= m; j ++) { cin>>a[i][j]; max1 = max(max1, a[i][j]); if((i + j) % 2 == 0) { s0 += a[i][j]; c0 ++; } else { s1 += a[i][j]; c1 ++; } } } if(c0 != c1) { int x = (s0 - s1) / (c0 - c1); // c0 > c1 if(x >= max1 && check(x)) { cout<<x * c1 - s1<<endl; } else { cout<<-1<<endl; } } else { if(s0 != s1) { cout<<-1<<endl; } else { int top = INF >> 1, end = max1 - 1; while(end + 1 != top) { int mid = (top + end) >> 1; if(check(mid)) { // >= mid is OK top = mid; } else { end = mid; } } if(top == INF >> 1) { cout<<-1<<endl; } else { cout<<top * c1 - s1<<endl; } } } } return 0; } ```