有一个棋盘,每次你可以选相邻的两个位置都加一。
问你最少要多少次操作才能让棋盘上的数都变成一样的,如果不能就输出 -1。
奇怪游戏 / 奇怪的游戏
题目大意
有一个棋盘,每次你可以选相邻的两个位置都加一。
问你最少要多少次操作才能让棋盘上的数都变成一样的,如果不能就输出 -1。
思路
看到相邻的位置想到把图黑白染色。
然后要把数变成一样你就可以先到用网络流来搞,判断它是否流满的方法来看是否变成一样。
但它要的是最少次的操作,网络流是求最大流,自然想到用二分。
然后开始搞具体实现。
首先你二分的肯定是最后变成的数 X,那黑色点的个数是 num0,权值和是 sum0,白色的个数是 num1,权值和是 sum1,那我们就是要找一个最小的 X,使得这个式子被满足:
num0∗X−sum0=num1∗X−sum1(因为你操作一次就相当于给 sum0,sum1 都加一)
然后化一下式子,得到:X=sum0−sum1num0−num1。
那如果 num0=num1,那除数就变成了 0,那就出问题了,这个时候是什么鬼呢?
我们考虑从 x∗y=z,得到 x=zy,那当 y=0,z=0 时,x 可以是任意的数,因为任意的数乘 0 都是 0。
那也就是说,当 sum0=sum1 且 num0=num1 时,就会有多组解,那这个时候我们就要上二分,通过用网络流来验证这个答案行不行,然后找到最小的那个。
那如果 y=0,z≠0,就是 x 什么实数都不行,因为没有实数乘 0 会变成非零数。那也就说当 num0=num1 且 sum0≠sum1 的时候,就是无解的,直接输出 −1。
那如果 num0≠num1,那你会发现,这个 X 的解是唯一的,而且你还能把它算出来。
但是你算出来之后你还要用网络流验算一下,如果不是还是要输出 −1。
那接着新的问题又来了,如果构网络流的图(是跑最大流应该很显然了吧)。
那我们考虑这样,假设你二分的是 X,点 (i,j) 权值为 ai,j。
源点连线黑色点 (x1,y1),流量是 X−ax1,y1。
表示这个黑点最多能被加多少次。
那现在只有黑色点能被加,我们就把黑色点连向它旁边的白色点,流量无限。
这样就实现了黑点和白点捆绑加,你加黑点就一定要把其中一个旁边的白点也加了,不然不能流到白点。
那白点又能被加多少次呢,白色 (x2,y2) 连汇点,流量是 X−ax2,y2。(流量就是它能被加的次数)
那如果判断这个方案是否可行呢?
我们可以跑最大流,然后看是否已经流满,就看最大流是否等于所有 X−ax1,y1 的和。
(当然你用 X−ax2,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__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现