【BZOJ2756】奇怪的游戏(SCOI2012)-分类讨论+二分+最大流
测试地址:奇怪的游戏
做法:本题需要用到分类讨论+二分+最大流。
首先看到棋盘,先黑白染色,然后我们发现每次操作两个相邻的格子一定是不同色的,意味着黑白格子得到增加的权值和是一样的。那么令为最后得到的数字,为黑/白色格子的数量和权值和,有:
解得:。
当的时候,我们直接解出,那么现在我们就要判定存不存在解了。实际上,现在就是把每个点的操作次数(即)分配给和它相邻的边,为了方便我们把黑色格子作为分配的点,并且使得分配完之后所有白色格子也得到正好的次数。我们发现这就是一个流量平衡的关系,因此我们从源点向每个黑色格子,从每个白色格子向汇点连一条容量为的边,从每个黑色格子向与它相邻的白色格子连一条容量无限大的边,那么问题有解当且仅当这个网络的最大流等于。
那么当时,我们知道为偶数,那么若显然无解,否则我们知道,若对于一个它是有解的,那么对于它也是有解的,因为我们显然可以找到一种方式,使得操作完后所有格子的权值都正好加(相当于用骨牌覆盖整个棋盘)。因此我们二分,用上述的方法判定即可。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=(1ll<<50);
int Test,n,m,S,T,first[2010],tot;
int h,t,q[2010],lvl[2010],cur[2010];
ll a[45][45],sum[2],num[2];
struct edge
{
int v,next;
ll f;
}e[50010];
void insert(int a,int b,ll f)
{
e[++tot].v=b,e[tot].next=first[a],e[tot].f=f,first[a]=tot;
e[++tot].v=a,e[tot].next=first[b],e[tot].f=0,first[b]=tot;
}
bool makelevel()
{
for(int i=1;i<=T;i++)
lvl[i]=-1,cur[i]=first[i];
h=t=1;
q[1]=S;
lvl[S]=0;
while(h<=t)
{
int v=q[h++];
for(int i=first[v];i;i=e[i].next)
if (e[i].f&&lvl[e[i].v]==-1)
{
lvl[e[i].v]=lvl[v]+1;
q[++t]=e[i].v;
}
}
return lvl[T]!=-1;
}
ll maxflow(int v,ll maxf)
{
ll ret=0,f;
if (v==T) return maxf;
for(int i=cur[v];i;i=e[i].next)
{
if (e[i].f&&lvl[e[i].v]==lvl[v]+1)
{
f=maxflow(e[i].v,min(maxf-ret,e[i].f));
ret+=f;
e[i].f-=f;
e[i^1].f+=f;
if (ret==maxf) break;
}
cur[v]=i;
}
if (!ret) lvl[v]=-1;
return ret;
}
bool check(ll x)
{
memset(first,0,sizeof(first));
tot=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if ((i+j)%2)
{
insert(S,(i-1)*m+j,x-a[i][j]);
if (i>1) insert((i-1)*m+j,(i-2)*m+j,inf);
if (i<n) insert((i-1)*m+j,i*m+j,inf);
if (j>1) insert((i-1)*m+j,(i-1)*m+j-1,inf);
if (j<m) insert((i-1)*m+j,(i-1)*m+j+1,inf);
}
else insert((i-1)*m+j,T,x-a[i][j]);
}
ll maxf=0;
while(makelevel())
maxf+=maxflow(S,inf);
if (maxf!=num[0]*x-sum[0]) return 0;
else return 1;
}
int main()
{
scanf("%d",&Test);
while(Test--)
{
ll maxa=0;
scanf("%d%d",&n,&m);
S=n*m+1,T=n*m+2;
sum[0]=sum[1]=num[0]=num[1]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
scanf("%lld",&a[i][j]);
maxa=max(maxa,a[i][j]);
sum[(i+j)%2]+=a[i][j],num[(i+j)%2]++;
}
if (num[0]!=num[1])
{
ll x=(sum[0]-sum[1])/(num[0]-num[1]);
if (check(x)) printf("%lld\n",num[0]*x-sum[0]);
else printf("-1\n");
}
else
{
if (sum[0]!=sum[1]) {printf("-1\n");continue;}
ll l=maxa,r=inf;
while(r>l)
{
ll mid=(l+r)>>1;
if (check(mid)) r=mid;
else l=mid+1;
}
printf("%lld\n",num[0]*l-sum[0]);
}
}
return 0;
}