BZOJ 2756 SCOI 奇怪的游戏
2756: [SCOI2012]奇怪的游戏
Time Limit: 40 Sec Memory Limit: 128 MBSubmit: 4978 Solved: 1381
[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
这道题的思路很妙啊
对棋盘进行黑白染色
设黑格个数为num1 数值和为sum1
设白格个数为num1 数值和为sum1
设最后都变为x
则
num1 * x – sum1 = num2 * x – sum2
x = (sum1 – sum2) / (num1 – num2)
当num1 ≠ num2时 可以解出 x 再用网络流check即可
对于num1 = num2时 可以发现 对于一个合法的x k>=x都是一个合法的解
因为num1 = num2 => (num1 + num2) % 2 == 0 可以构造一层的满覆盖
所以可以二分x 然后用网络流check
建图:
如果点k为白
建边(s, k, x – v[k])
如果为黑
建边(k, t, x – v[k])
对相邻点u、v (u为白)
建边 (u, v, inf)
#include <bits/stdc++.h> #define ll long long #define p(x,y) (x-1)*m+y #define inf (1LL<<50) using namespace std; inline int read(){ int x=0;int f=1;char ch=getchar(); while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();} while(isdigit(ch)) {x=x*10+ch-'0';ch=getchar();} return x*f; } const int MAXN=2e4+10; const int dx[4]={0,1,-1,0}; const int dy[4]={1,0,0,-1}; struct node{ int y,next,back; ll flow; }e[MAXN]; int linkk[MAXN],len=0,n,m,level[3000],head,tail,s,t,q[MAXN],v[50][50],col[50][50],num1,num2; ll sum1,sum2,l,r; inline void insert(int x,int y,ll f){ e[++len].y=y;e[len].next=linkk[x];linkk[x]=len;e[len].flow=f;e[len].back=len+1; e[++len].y=x;e[len].next=linkk[y];linkk[y]=len;e[len].flow=0;e[len].back=len-1; } inline bool getlevel(){ head=tail=0; memset(level,-1,sizeof(level)); q[++tail]=s;level[s]=0; while(head<tail){ int tn=q[++head]; for(int i=linkk[tn];i;i=e[i].next){ if(level[e[i].y]==-1&&e[i].flow){ level[e[i].y]=level[tn]+1; q[++tail]=e[i].y; } } } return level[t]>=0; } inline ll getmaxflow(int x,ll flow){ if(x==t) return flow; ll f=0,d; for(int i=linkk[x];i;i=e[i].next){ if(level[e[i].y]==level[x]+1&&e[i].flow){ if(d=getmaxflow(e[i].y,min(flow-f,e[i].flow))){ f+=d;e[i].flow-=d;e[e[i].back].flow+=d; if(f==flow) return f; } } } if(f==0) level[x]=-1; return f; } inline ll dinic(){ ll ans=0,d; while(getlevel()){ while(d=getmaxflow(s,inf)) ans+=d; } return ans; } inline bool check(ll x){ memset(linkk,0,sizeof(linkk)); len=0;ll sum=0; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(col[i][j]){ insert(p(i,j),t,x-v[i][j]); } else{ insert(s,p(i,j),x-v[i][j]);sum+=x-v[i][j]; for(int k=0;k<4;k++){ int tx=i+dx[k];int ty=j+dy[k]; if(tx<1||ty<1||tx>n||ty>m) continue; insert(p(i,j),p(tx,ty),inf); } } } } return dinic()==sum; } int main(){ int T=read(); while(T--){ n=read();m=read();s=0;t=p(n+1,1);sum1=sum2=num1=num2=0; l=0,r=inf;int mx=0; memset(col,0,sizeof(col)); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ v[i][j]=read();mx=max(v[i][j],mx); if((i+j)&1) col[i][j]=1,sum1+=v[i][j],num1++; else num2++,sum2+=v[i][j]; } } if(num1!=num2){ ll x=(sum2-sum1)/(num2-num1); if(x>=mx&&check(x)) printf("%lld\n",x*num1-sum1); else printf("-1\n"); } else{ int mn=-1;l=mx; if(sum1!=sum2){ printf("-1\n");continue; } while(l<=r){ ll mid=(l+r)>>1; if(check(mid)) mn=mid,r=mid-1; else l=mid+1; } if(mn==-1) printf("-1\n"); else printf("%lld\n",1LL*mn*num1-sum1); } } return 0; }