2756: [SCOI2012]奇怪的游戏
Time Limit: 40 Sec Memory Limit: 128 MBSubmit: 2959 Solved: 807
[ Submit][ Status][ Discuss]
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
2 2
1 2
2 3
3 3
1 2 3
2 3 4
4 3 2
Sample Output
2
-1
-1
HINT
【数据范围】
对于30%的数据,保证 T<=10,1<=N,M<=8
对于100%的数据,保证 T<=10,1<=N,M<=40,所有数为正整数且小于1000000000
Source
题解:二分+网络流判定
设最后所有格子里的数为k,可以列出式子num_odd*k-sum_odd=num_even*k-sum_even
如果n*m是奇数,那么可以解出k=(sum_odd-sum_even)/(num_odd-num_even);
如果是偶数的话,我们可以二分。因为如果是偶数的话我们对于整个棋盘进行棋盘覆盖,就可以使所有的数都+1.所有满足二分的性质。
考虑建图判定。
对棋盘进行黑白染色。
S->黑点
白点->T
相邻点之间连inf。然后黑白分别到源汇连val-K。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#define N 50
#define M 20000
#define LL long long
#define inf 2000000000
using namespace std;
int n,m,t,cnt;
int tot,point[M],next[M],v[M],deep[M],num[M],cur[M];
int map[N][N],pos[N][N],last[M],num1,num2;
LL sum1,sum2,remain[M],h[N][N];
int xx[10]={0,-1,0,1},yy[10]={-1,0,1,0};
void add(int x,int y,LL z)
{
tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z;
tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0;
//cout<<x<<" "<<y<<" "<<z<<endl;
}
LL addflow(int s,int t)
{
int now=t; LL ans=1e18;
while (now!=s)
{
ans=min(ans,remain[last[now]]);
now=v[last[now]^1];
}
now=t;
while (now!=s)
{
remain[last[now]]-=ans;
remain[last[now]^1]+=ans;
now=v[last[now]^1];
}
return ans;
}
void bfs(int s,int t)
{
queue<int> p;
for (int i=s;i<=t;i++) deep[i]=t;
deep[t]=0; p.push(t);
while (!p.empty())
{
int now=p.front(); p.pop();
for (int i=point[now];i!=-1;i=next[i])
if (deep[v[i]]==t&&remain[i^1])
deep[v[i]]=deep[now]+1,p.push(v[i]);
}
}
LL isap(int s,int t)
{
bfs(s,t);
for (int i=s;i<=t;i++) cur[i]=point[i];
for (int i=s;i<=t;i++) num[deep[i]]++;
int now=s; LL ans=0;
while (deep[s]<t)
{
if (now==t)
{
ans+=addflow(s,t);
now=s;
}
bool f=false;
for (int i=cur[now];i!=-1;i=next[i])
if (deep[now]==deep[v[i]]+1&&remain[i])
{
cur[now]=i;
last[v[i]]=i;
f=true;
now=v[i];
break;
}
if (!f)
{
int minn=t;
for (int i=point[now];i!=-1;i=next[i])
if (remain[i]) minn=min(minn,deep[v[i]]);
if (!--num[deep[now]]) break;
deep[now]=minn+1;
num[deep[now]]++;
cur[now]=point[now];
if (now!=s)
now=v[last[now]^1];
}
}
return ans;
}
bool pd(LL height)
{
tot=-1;
memset(point,-1,sizeof(point));
memset(next,-1,sizeof(next));
memset(num,0,sizeof(num));
int s=1; int t=cnt+1;
LL sum=0,sum1=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
if (map[i][j])
{
add(s,pos[i][j],height-h[i][j]);
sum+=height-h[i][j];
for (int k=0;k<4;k++)
{
int nowx=i+xx[k];
int nowy=j+yy[k];
if (nowx>0&&nowy>0&&nowx<=n&&nowy<=m)
add(pos[i][j],pos[nowx][nowy],1e18);
}
}
else add(pos[i][j],t,height-h[i][j]),sum1+=height-h[i][j];
}
LL tt=isap(s,t);
if (tt==sum&&tt==sum1) return true;
else return false;
}
int main()
{
scanf("%d",&t);
for (int T=1;T<=t;T++)
{
scanf("%d%d",&n,&m);
tot=-1; sum1=0; sum2=0; LL maxn=0; cnt=1;
num1=0; num2=0;
memset(point,-1,sizeof(point));
memset(next,-1,sizeof(next));
memset(num,0,sizeof(num));
memset(map,0,sizeof(map));
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
scanf("%lld",&h[i][j]),maxn=max(maxn,h[i][j]);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
pos[i][j]=++cnt;
if (i%2)
{
if (j%2) map[i][j]=1,sum1+=h[i][j],num1++;
else sum2+=h[i][j],num2++;
}
else if (j%2==0) map[i][j]=1,sum1+=h[i][j],num1++;
else sum2+=h[i][j],num2++;
}
if ((n*m)%2)
{
LL t=(sum1-sum2)/(num1-num2);
if (!pd(t)||t<maxn) printf("-1\n");
else printf("%lld\n",(t*n*m-sum1-sum2)/2);
continue;
}
LL l=maxn; LL r=inf;
LL ans=inf;
while (l<=r)
{
LL mid=(l+r)/2;
if (pd(mid)) ans=min(ans,mid),r=mid-1;
else l=mid+1;
}
if (ans==inf) printf("-1\n");
else printf("%lld\n",(ans*n*m-sum1-sum2)/2);
}
}