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());
}
}