bzoj2756 [SCOI2012]奇怪的游戏

来源:互联网 发布:淘宝规则名词解释 编辑:程序博客网 时间:2024/06/05 07:40

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 longusing 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());    }}
原创粉丝点击