题目描述:
后来发现原来此题不过如此。
对原棋盘进行黑白染色。设黑色格点有num1个,数值和为sum1;白色格点有num2个,数值和为sum2。设最后所有的数都变成了x,则有:
x * num1 - sum1 = x * num2 - sum2,即:
(num1 - num2)x = sum1 - sum2。
I. 若num1 = num2,那么就二分答案并检验。
II. 若num1 != num2,那么只需要保证:
1) (num1 - num2) | (sum1 - sum2);
2) x >= 棋盘中的最大数;
3) 能通过最大流的检验。
网络建模时,从S点向白色格点建边,流量为x减去格点上的数;从黑色格点向T建边,流量为x减去格点上的数。另外从白色格点向上下左右建边(不超出边界),流量为正无穷。
若求出来的最大流的二倍刚好等于所有格点需要被加上的数的总和,那么此x满足条件,否则不满足条件。
Description
Blinker最近喜欢上一个奇怪的游戏。
这个游戏在一个 N*M 的棋盘上玩,每个格子有一个数。每次 Blinker 会选择两个相邻的格子,并使这两个数都加上 1。
现在 Blinker 想知道最少多少次能使棋盘上的数都变成同一个数,如果永远不能变成同一个数则输出-1。
Input
输入的第一行是一个整数T,表示输入数据有T轮游戏组成。
每轮游戏的第一行有两个整数N和M, 分别代表棋盘的行数和列数。
接下来有N行,每行 M个数。
Output
对于每个游戏输出最少能使游戏结束的次数,如果永远不能变成同一个数则输出-1。
Sample Input
2
2 2
1 2
2 3
3 3
1 2 3
2 3 4
4 3 2
Sample Output
2
-1
HINT
【数据范围】
对于30%的数据,保证 T<=10,1<=N,M<=8
对于100%的数据,保证 T<=10,1<=N,M<=40,所有数为正整数且小于1000000000
想了很久,一直想用线性规划的思想进行网络建模,但行不通。后来发现原来此题不过如此。
对原棋盘进行黑白染色。设黑色格点有num1个,数值和为sum1;白色格点有num2个,数值和为sum2。设最后所有的数都变成了x,则有:
x * num1 - sum1 = x * num2 - sum2,即:
(num1 - num2)x = sum1 - sum2。
I. 若num1 = num2,那么就二分答案并检验。
II. 若num1 != num2,那么只需要保证:
1) (num1 - num2) | (sum1 - sum2);
2) x >= 棋盘中的最大数;
3) 能通过最大流的检验。
网络建模时,从S点向白色格点建边,流量为x减去格点上的数;从黑色格点向T建边,流量为x减去格点上的数。另外从白色格点向上下左右建边(不超出边界),流量为正无穷。
若求出来的最大流的二倍刚好等于所有格点需要被加上的数的总和,那么此x满足条件,否则不满足条件。
Sap代码(注释部分为递归的Sap):
/*****************************************************\
* @prob: SCOI2012 game * @auth: Wang Junji *
* @stat: Accepted.(非递归) TLE: 60(递归) *
* @date: May. 28th, 2012 * @memo: 最大流、二分检验 *
\*****************************************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
#define pos(i, j) (((i) * m) + (j) + 1)
typedef long long int64;
const int maxR = 50, maxN = 1610, maxCNT = 100000;
const int64 INF = 0x3f3f3f3f3f3f3f3fLL;
struct Edge
{
int v; int64 f; Edge *next, *back; Edge() {}
Edge(int v, int64 f, Edge *next): v(v), f(f), next(next) {}
void set(int _v, int64 _f, Edge *_next)
{v = _v, f = _f, next = _next; return;}
} *edge[maxN], Tmp[maxCNT];
int64 mp[maxR][maxR], sum_even, sum_odd;
int d[maxN], cnt[maxN], n, m, N, S, T, t, cnt_Tmp;
inline void Ins(int u, int v, int64 f)
{
if (cnt_Tmp < maxCNT)
Tmp[cnt_Tmp].set(v, f, edge[u]),
edge[u] = Tmp + cnt_Tmp++;
else edge[u] = new Edge(v, f, edge[u]);
if (cnt_Tmp < maxCNT)
Tmp[cnt_Tmp].set(u, 0, edge[v]),
edge[v] = Tmp + cnt_Tmp++;
else edge[v] = new Edge(u, 0, edge[v]);
edge[u] -> back = edge[v];
edge[v] -> back = edge[u];
return;
}
/*
int64 Sap(int u, int64 Lim)
{
if (u == T) return Lim; int64 tmp = 0;
for (Edge *p = edge[u]; p; p = p -> next)
if (p -> f > 0 && d[u] == d[p -> v] + 1)
{
int64 k = Sap(p -> v, std::min(p -> f, Lim - tmp));
p -> f -= k, p -> back -> f += k;
if ((tmp += k) >= Lim) return tmp;
}
if (d[S] >= N) return tmp;
if (!(--cnt[d[u]])) d[S] = N;
++cnt[++d[u]]; return tmp;
}
*/
inline int64 Sap()
{
static Edge *cur[maxN], *p; int64 ans = 0;
static int pre[maxN]; pre[S] = S;
memcpy(cur, edge, sizeof edge);
for (int u = S; d[S] < N;)
{
if (u == T)
{
int64 max_flow = INF;
for (int i = S; i - T; i = cur[i] -> v)
max_flow = std::min(max_flow, cur[i] -> f);
for (int i = S; i - T; i = cur[i] -> v)
cur[i] -> f -= max_flow, cur[i] -> back -> f += max_flow;
ans += max_flow, u = S;
}
for (p = cur[u]; p; p = p -> next)
if (p -> f > 0 && d[u] == d[p -> v] + 1)
{
cur[u] = p, pre[p -> v] = u, u = p -> v;
break;
}
if (!p)
{
if (!(--cnt[d[u]])) break;
cur[u] = edge[u]; int min_d = N;
for (Edge *p = edge[u]; p; p = p -> next)
if (p -> f > 0) min_d = std::min(min_d, d[p -> v]);
++cnt[d[u] = min_d + 1], u = pre[u];
}
}
return ans;
}
inline bool check(int64 x)
{
memset(edge, 0, sizeof edge); cnt_Tmp = 0;
memset(cnt, 0, sizeof cnt); cnt[0] = N;
memset(d, 0, sizeof d);
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
if ((i + j) & 1)
{
Ins(S, pos(i, j), x - mp[i][j]);
if (i) Ins(pos(i, j), pos(i - 1, j), INF);
if (j) Ins(pos(i, j), pos(i, j - 1), INF);
if (i < n - 1) Ins(pos(i, j), pos(i + 1, j), INF);
if (j < m - 1) Ins(pos(i, j), pos(i, j + 1), INF);
}
else Ins(pos(i, j), T, x - mp[i][j]);
return (Sap() << 1) == (x * n * m - sum_odd - sum_even);
}
inline int64 gettime(int64 x) {return (x * n * m - sum_odd - sum_even) >> 1;}
int main()
{
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
scanf("%d", &t);
while (t--)
{
int num_odd = 0, num_even = 0; int64 max_mp = 0;
sum_odd = 0, sum_even = 0;
scanf("%d%d", &n, &m);
S = n * m + 1, T = n * m + 2, N = n * m + 2;
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
{
scanf("%lld", mp[i] + j);
if ((i + j) & 1) ++num_odd, sum_odd += mp[i][j];
else ++num_even, sum_even += mp[i][j];
max_mp = std::max(max_mp, mp[i][j]);
}
if (num_odd - num_even)
{
int64 x;
if ((sum_odd - sum_even) % (num_odd - num_even) || (x =
(sum_odd - sum_even) / (num_odd - num_even)) < max_mp
|| !check(x))
{
printf("-1\n");
continue;
}
printf("%lld\n", gettime(x));
}
else
{
int64 L = max_mp, R = INF;
while (L < R)
{
int64 Mid = (L + R) >> 1;
if (check(Mid)) R = Mid;
else L = Mid + 1;
}
printf("%lld\n", check(R) ? gettime(R) : -1);
}
}
return 0;
}
Dinic代码(注释部分为递归的Dinic):
/*******************************************\
* @prob: SCOI2012 game *
* @auth: Wang Junji *
* @stat: Accepted.(非递归), TLE: 0(递归) *
* @date: May. 28th, 2012 *
* @memo: 最大流、二分检验 *
\*******************************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
#define pos(i, j) (((i) * m) + (j) + 1)
typedef long long int64;
const int maxR = 50, maxN = 1610, maxCNT = 100000;
const int64 INF = 0x3f3f3f3f3f3f3f3fLL;
struct Edge
{
int v; int64 f; Edge *next, *back; Edge() {}
Edge(int v, int64 f, Edge *next): v(v), f(f), next(next) {}
void set(int _v, int64 _f, Edge *_next)
{v = _v, f = _f, next = _next; return;}
} *edge[maxN], Tmp[maxCNT];
int64 mp[maxR][maxR], sum_even, sum_odd;
int d[maxN], n, m, N, S, T, t, cnt_Tmp;
inline void Ins(int u, int v, int64 f)
{
if (cnt_Tmp < maxCNT)
Tmp[cnt_Tmp].set(v, f, edge[u]),
edge[u] = Tmp + cnt_Tmp++;
else edge[u] = new Edge(v, f, edge[u]);
if (cnt_Tmp < maxCNT)
Tmp[cnt_Tmp].set(u, 0, edge[v]),
edge[v] = Tmp + cnt_Tmp++;
else edge[v] = new Edge(u, 0, edge[v]);
edge[u] -> back = edge[v];
edge[v] -> back = edge[u];
return;
}
inline bool Bfs()
{
static const int SIZE = 0xffff;
static int q[SIZE + 1];
int f = 0, r = 0, u, v; Edge *p;
memset(d, 0xff, sizeof d);
for (d[q[r++] = S] = 0; f - r;)
for (p = edge[u = q[f++]], f &= SIZE; p; p = p -> next)
if (d[v = p -> v] == -1 && p -> f > 0)
d[q[r++] = v] = d[u] + 1, r &= SIZE;
return d[T] + 1;
}
/*
int64 Dfs(int u, int64 Lim)
{
if (u == T) return Lim; int64 tmp = 0;
for (Edge *p = edge[u]; p; p = p -> next)
if (p -> f > 0 && d[u] == d[p -> v] - 1)
{
int64 k = Dfs(p -> v, std::min(p -> f, Lim - tmp));
p -> f -= k, p -> back -> f += k;
if ((tmp += k) >= Lim) return tmp;
}
return tmp;
}
*/
inline int64 Aug()
{
static int sta[maxN];
static Edge *take[maxN], *cur[maxN];
memcpy(cur, edge, sizeof edge);
int top = 0, u, v; int64 ans = 0;
for (sta[top++] = S; top; )
if ((u = sta[top - 1]) == T)
{
int64 max_flow = INF;
for (int i = 1; i < top; ++i)
max_flow = std::min(max_flow, take[i] -> f);
ans += max_flow;
for (int i = top - 1; i > 0; --i)
{
take[i] -> f -= max_flow;
take[i] -> back -> f += max_flow;
if (!(take[i] -> f)) top = i;
}
}
else
{
for (; cur[u]; cur[u] = cur[u] -> next)
if (cur[u] -> f > 0 && d[u] == d[v = cur[u] -> v] - 1)
break;
if (cur[u]) sta[top] = v, take[top++] = cur[u];
else --top, d[u] = -1;
}
return ans;
}
inline bool check(int64 x)
{
memset(edge, 0, sizeof edge); cnt_Tmp = 0;
memset(d, 0, sizeof d);
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
if ((i + j) & 1)
{
Ins(S, pos(i, j), x - mp[i][j]);
if (i) Ins(pos(i, j), pos(i - 1, j), INF);
if (j) Ins(pos(i, j), pos(i, j - 1), INF);
if (i < n - 1) Ins(pos(i, j), pos(i + 1, j), INF);
if (j < m - 1) Ins(pos(i, j), pos(i, j + 1), INF);
}
else Ins(pos(i, j), T, x - mp[i][j]);
int64 ans = 0; while (Bfs()) ans += Aug();
return (ans << 1) == (x * n * m - sum_odd - sum_even);
}
inline int64 gettime(int64 x) {return (x * n * m - sum_odd - sum_even) >> 1;}
int main()
{
freopen("game.in", "r", stdin);
freopen("game.out", "w", stdout);
scanf("%d", &t);
while (t--)
{
int num_odd = 0, num_even = 0; int64 max_mp = 0;
sum_odd = 0, sum_even = 0;
scanf("%d%d", &n, &m);
S = n * m + 1, T = n * m + 2, N = n * m + 2;
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j)
{
scanf("%lld", mp[i] + j);
if ((i + j) & 1) ++num_odd, sum_odd += mp[i][j];
else ++num_even, sum_even += mp[i][j];
max_mp = std::max(max_mp, mp[i][j]);
}
if (num_odd - num_even)
{
int64 x;
if ((sum_odd - sum_even) % (num_odd - num_even) || (x =
(sum_odd - sum_even) / (num_odd - num_even)) <= max_mp
|| !check(x))
{
printf("-1\n");
continue;
}
printf("%lld\n", gettime(x));
}
else
{
int64 L = max_mp, R = INF >> 30, res = 0;
while (L < R)
{
int64 Mid = (L + R) >> 1;
if (check(Mid)) res = R = Mid;
else L = Mid + 1;
}
printf("%lld\n", res ? gettime(R) : -1);
}
}
return 0;
}
只不过此题看来,用时:递归的Dinic > 递归的Sap > 非递归的Dinic ≈ 非递归的Sap
所以Sap算法在稀疏图上还是很优秀的!