bzoj2756 [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

分析:
这道题需要分情况讨论:
首先要明确,黑块和白块是捆绑销售的,所以黑块一共增加了多少,白块就增加了多少

情况一:

n*m是偶数
那么一定可以用骨牌(两格)不重不漏的覆盖所有棋盘
若X可行,那么X+1一定可行,这符合二分的条件,所以二分+判定

情况二:

n*m是奇数
这个时候就有方程:
X(达到的相等数值)
Bs 黑块上的原始数字之和 Bn 黑块数目
Ws 白块上的原始数字之和 Wn 白块数目

X*Bn-Bs=X*Wn-Ws

解出X,用网络流判断是否可行

注意一下二分的下界是棋盘中的最大值
(棋盘中的数只加不减)

简单说一下建图吧:
把棋盘黑白染色,黑色格子作为第一部,白色格子作为第二部
源点向一部连边,容量是二分(计算)出的答案-z[i][j]
二部向汇点连边,容量是二分(计算)出的答案-z[i][j]
在两个部之间,相邻格子连容量为INF的边

tip

开ll

不保证代码的正确性,但是这个网站上的代码是对的
http://www.cnblogs.com/jianglangcaijin/p/3799797.html

这里写代码片
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

const int N=2005;
const ll INF=0x33333333;
struct node{
    int x,y,nxt;
    ll v;
};
node way[20001];
int tot,st[N],cur[N],deep[N],s,t;
int q[N],tou,wei,n,m,Bn,Wn;
ll Bs,Ws,z[45][45],mx;

inline void add(int u,int w,ll z)
{
    tot++;
    way[tot].x=u;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
    tot++;
    way[tot].x=w;way[tot].y=u;way[tot].v=0;way[tot].nxt=st[w];st[w]=tot;
}

inline int get(int x,int y){return (x-1)*n+y;}

inline ll max(ll a,ll b){if (a>b) return a;else return b;}

int bfs(int s,int t)
{
    memset(deep,-1,sizeof(deep));
    tou=wei=0;
    q[++wei]=s;
    deep[s]=1;
    for (int i=s;i<=t;i++) cur[i]=st[i];
    do
    {
        int r=q[++tou];
        for (int i=st[r];i!=-1;i=way[i].nxt)
            if (way[i].v&&deep[way[i].y]==-1)
            {
                deep[way[i].y]=deep[r]+1;
                q[++wei]=way[i].y;
            }
    }while (tou<wei);
    return deep[t]!=-1;
}

ll dfs(int now,int t,ll limit)
{
    if (!limit||now==t) return limit;
    int i;
    ll flow=0,f;
    for (i=cur[now];i!=-1;i=way[i].nxt)
    {
        cur[now]=i;
        if (deep[way[i].y]==deep[now]+1&&way[i].v&&(f=dfs(way[i].y,t,min(limit,way[i].v))))
        {
            flow+=f;
            limit-=f;
            way[i].v-=f;
            way[i^1].v+=f;
            if (!limit) break;
        }
    }
    return flow;
}

ll doit(ll num)
{
    ll ans=0;
    while (bfs(s,t))
        ans+=dfs(s,t,INF);
    return ans==num;  //与目标流量一样 
}

int check(ll x)
{
    memset(st,-1,sizeof(st));
    tot=-1;
    int i,j;
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
        {
            if (x<z[i][j]) return 0;
            if ((i+j)%2==0)  //B
            {
                add(s,get(i,j),x-z[i][j]);
                if (i>1) add(get(i,j),get(i-1,j),INF);
                if (j>1) add(get(i,j),get(i,j-1),INF);
                if (i<n) add(get(i,j),get(i+1,j),INF);
                if (j<m) add(get(i,j),get(i,j+1),INF);
            }
            else add(get(i,j),t,x-z[i][j]);
        }
    if (doit(x*Bn-Bs)) return 1;
    else return 0;
}

ll erfen()
{
    if (Bs!=Ws) return -1;
    ll l=mx,r=INF;
    ll mid;
    while (l<r)
    {
        mid=(l+r)>>1;
        if (check(mid))
           r=mid;
        else l=mid+1;
    }
    return l*Bn-Bs;
}

ll solve()
{
    s=0;t=n*m+1;
    if (n*m%2==0)
       return erfen();
    else
    {
        ll X=(Bs-Ws)/(Bn-Wn);
        if (check(X)) return Bn*X-Bs;
        else return -1;
    }
}

int main()
{
    int T;
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;i++)
            for (int j=1;j<=m;j++)
            {
                scanf("%lld",&z[i][j]); 
                if ((i+j)%2==0) Bs+=z[i][j],Bn++;   //黑块上的数和 
                else Ws+=z[i][j],Wn++;
                mx=max(mx,z[i][j]);   //二分下限,棋盘上的数只加不减 
            }
        printf("%lld",solve());
    }
}
原文地址:https://www.cnblogs.com/wutongtong3117/p/7673435.html