【ybt金牌导航3-2-4】【luogu P5038】奇怪游戏 / 奇怪的游戏

奇怪游戏 / 奇怪的游戏

题目链接:ybt金牌导航3-2-4 / luogu P5038

题目大意

有一个棋盘,每次你可以选相邻的两个位置都加一。
问你最少要多少次操作才能让棋盘上的数都变成一样的,如果不能就输出 -1。

思路

看到相邻的位置想到把图黑白染色。
然后要把数变成一样你就可以先到用网络流来搞,判断它是否流满的方法来看是否变成一样。
但它要的是最少次的操作,网络流是求最大流,自然想到用二分。

然后开始搞具体实现。
首先你二分的肯定是最后变成的数 X,那黑色点的个数是 num0,权值和是 sum0,白色的个数是 num1,权值和是 sum1,那我们就是要找一个最小的 X,使得这个式子被满足:
num0Xsum0=num1Xsum1(因为你操作一次就相当于给 sum0,sum1 都加一)

然后化一下式子,得到:X=sum0sum1num0num1
那如果 num0=num1,那除数就变成了 0,那就出问题了,这个时候是什么鬼呢?

我们考虑从 xy=z,得到 x=zy,那当 y=0,z=0 时,x 可以是任意的数,因为任意的数乘 0 都是 0
那也就是说,当 sum0=sum1num0=num1 时,就会有多组解,那这个时候我们就要上二分,通过用网络流来验证这个答案行不行,然后找到最小的那个。
那如果 y=0,z0,就是 x 什么实数都不行,因为没有实数乘 0 会变成非零数。那也就说当 num0=num1sum0sum1 的时候,就是无解的,直接输出 1

那如果 num0num1,那你会发现,这个 X 的解是唯一的,而且你还能把它算出来。
但是你算出来之后你还要用网络流验算一下,如果不是还是要输出 1

那接着新的问题又来了,如果构网络流的图(是跑最大流应该很显然了吧)。
那我们考虑这样,假设你二分的是 X,点 (i,j) 权值为 ai,j
源点连线黑色点 (x1,y1),流量是 Xax1,y1
表示这个黑点最多能被加多少次。
那现在只有黑色点能被加,我们就把黑色点连向它旁边的白色点,流量无限。
这样就实现了黑点和白点捆绑加,你加黑点就一定要把其中一个旁边的白点也加了,不然不能流到白点。
那白点又能被加多少次呢,白色 (x2,y2) 连汇点,流量是 Xax2,y2。(流量就是它能被加的次数)

那如果判断这个方案是否可行呢?
我们可以跑最大流,然后看是否已经流满,就看最大流是否等于所有 Xax1,y1 的和。
(当然你用 Xax2,y2 也可以,反正你求出来的公式就已经让它们是相同的)

然后搞就可以了。

代码

#include<queue> #include<cstdio> #include<cstring> #include<iostream> #define ll long long #define INF 0x3f3f3f3f3f3f3f3f using namespace std; struct node { ll x, to, nxt, op; }e[500001]; ll TI, n, m, le[5001], lee[5001], S, T; ll a[41][41], l, r, KK, dis[5001], tot_num; ll dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1}; ll sum[2], num[2]; queue <int> q; void csh() { l = 0; r = INF; sum[0] = sum[1] = 0; num[0] = num[1] = 0; } void csh_wll() { memset(le, 0, sizeof(le)); KK = 0; tot_num = 0; } bool ck(ll x, ll y) { if (x < 1 || x > n) return 0; if (y < 1 || y > m) return 0; return 1; } void add(ll x, ll y, ll z) { e[++KK] = (node){z, y, le[x], KK + 1}; le[x] = KK; e[++KK] = (node){0, x, le[y], KK - 1}; le[y] = KK; } bool bfs() { for (ll i = 1; i <= tot_num; i++) { dis[i] = -1; lee[i] = le[i]; } while (!q.empty()) q.pop(); q.push(S); dis[S] = 0; while (!q.empty()) { ll now = q.front(); q.pop(); for (ll i = le[now]; i; i = e[i].nxt) if (e[i].x > 0 && dis[e[i].to] == -1) { dis[e[i].to] = dis[now] + 1; if (e[i].to == T) return 1; q.push(e[i].to); } } return 0; } ll dfs(ll now, ll sum) { if (now == T) return sum; ll go = 0; for (ll &i = lee[now]; i; i = e[i].nxt) if (e[i].x > 0 && dis[e[i].to] == dis[now] + 1) { ll this_go = dfs(e[i].to, min(sum - go, e[i].x)); if (this_go) { e[i].x -= this_go; e[e[i].op].x += this_go; go += this_go; if (go == sum) return go; } } if (go < sum) dis[now] = -1; return go; } ll dinic() { ll re = 0; while (bfs()) re += dfs(S, INF); return re; } bool check(ll now) { csh_wll(); ll needsum = 0; S = n * m + 1; T = n * m + 2; tot_num = T; for (ll i = 1; i <= n; i++) for (ll j = 1; j <= m; j++) { if ((i + j) & 1) { add(S, (i - 1) * m + j, now - a[i][j]);//起点->黑点 for (ll k = 0; k < 4; k++) if (ck(i + dx[k], j + dy[k])) add((i - 1) * m + j, (i + dx[k] - 1) * m + j + dy[k], INF);//黑点->它旁边的白点 } else add((i - 1) * m + j, T, now - a[i][j]), needsum += now - a[i][j];//白点->终点 } return dinic() == needsum;//判断是否流满,流满就代表可以 } int main() { scanf("%lld", &TI); while (TI--) { csh(); scanf("%lld %lld", &n, &m); for (ll i = 1; i <= n; i++) for (ll j = 1; j <= m; j++) { scanf("%lld", &a[i][j]); l = max(l, a[i][j]); sum[(i + j) & 1] += a[i][j]; num[(i + j) & 1]++; } if (num[0] == num[1]) { if (sum[0] != sum[1]) {//根据公式可以看出无解 printf("-1\n"); continue; } //否则有很多解,要通过二分找到次数最少的,也就是最后加到的值最小的 ll ans = -1, mid; while (l <= r) { mid = (l + r) >> 1; if (check(mid)) { ans = mid; r = mid - 1; } else l = mid + 1; } if (ans == -1) { printf("-1\n"); continue; } else printf("%lld\n", (n * m * ans - sum[0] - sum[1]) / 2); } else { ll X = (sum[0] - sum[1]) / (num[0] - num[1]);//直接算出答案 if (X >= l && check(X)) {//要验证 printf("%lld\n", (n * m * X - sum[0] - sum[1]) / 2); } else printf("-1\n"); } } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_3-2-4.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(31)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示