【最大流】【二分检验】【SCOI2012】奇怪的游戏

题目描述:
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算法在稀疏图上还是很优秀的!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值