标签:include span 最小 print dfs namespace ios int sum
有一个棋盘,每次你可以选相邻的两个位置都加一。
问你最少要多少次操作才能让棋盘上的数都变成一样的,如果不能就输出 -1。
看到相邻的位置想到把图黑白染色。
然后要把数变成一样你就可以先到用网络流来搞,判断它是否流满的方法来看是否变成一样。
但它要的是最少次的操作,网络流是求最大流,自然想到用二分。
然后开始搞具体实现。
首先你二分的肯定是最后变成的数 \(X\),那黑色点的个数是 \(num_0\),权值和是 \(sum_0\),白色的个数是 \(num_1\),权值和是 \(sum_1\),那我们就是要找一个最小的 \(X\),使得这个式子被满足:
\(num_0*X-sum_0=num_1*X-sum_1\)(因为你操作一次就相当于给 \(sum_0,sum_1\) 都加一)
然后化一下式子,得到:\(X=\dfrac{sum_0-sum_1}{num_0-num_1}\)。
那如果 \(num_0=num_1\),那除数就变成了 \(0\),那就出问题了,这个时候是什么鬼呢?
我们考虑从 \(x*y=z\),得到 \(x=\frac{z}{y}\),那当 \(y=0,z=0\) 时,\(x\) 可以是任意的数,因为任意的数乘 \(0\) 都是 \(0\)。
那也就是说,当 \(sum_0=sum_1\) 且 \(num_0=num_1\) 时,就会有多组解,那这个时候我们就要上二分,通过用网络流来验证这个答案行不行,然后找到最小的那个。
那如果 \(y=0,z\neq0\),就是 \(x\) 什么实数都不行,因为没有实数乘 \(0\) 会变成非零数。那也就说当 \(num_0=num_1\) 且 \(sum_0\neq sum_1\) 的时候,就是无解的,直接输出 \(-1\)。
那如果 \(num_0\neq num_1\),那你会发现,这个 \(X\) 的解是唯一的,而且你还能把它算出来。
但是你算出来之后你还要用网络流验算一下,如果不是还是要输出 \(-1\)。
那接着新的问题又来了,如果构网络流的图(是跑最大流应该很显然了吧)。
那我们考虑这样,假设你二分的是 \(X\),点 \((i,j)\) 权值为 \(a_{i,j}\)。
源点连线黑色点 \((x_1,y_1)\),流量是 \(X-a_{x_1,y_1}\)。
表示这个黑点最多能被加多少次。
那现在只有黑色点能被加,我们就把黑色点连向它旁边的白色点,流量无限。
这样就实现了黑点和白点捆绑加,你加黑点就一定要把其中一个旁边的白点也加了,不然不能流到白点。
那白点又能被加多少次呢,白色 \((x_2,y_2)\) 连汇点,流量是 \(X-a_{x_2,y_2}\)。(流量就是它能被加的次数)
那如果判断这个方案是否可行呢?
我们可以跑最大流,然后看是否已经流满,就看最大流是否等于所有 \(X-a_{x_1,y_1}\) 的和。
(当然你用 \(X-a_{x_2,y_2}\) 也可以,反正你求出来的公式就已经让它们是相同的)
然后搞就可以了。
#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;
}
【ybt金牌导航3-2-4】【luogu P5038】奇怪游戏 / 奇怪的游戏
标签:include span 最小 print dfs namespace ios int sum
原文地址:https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_3-2-4.html