【bzoj2756】【SCOI2012】【奇怪的游戏】【最大流+二分】

本文介绍了一种在N*M棋盘上进行的游戏算法,目标是最少操作次数使所有棋子数值相同。通过黑白染色、最大流算法实现解决方案,适用于特定规模的数据集。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 

题解:

           首先对图黑白染色.

           设白色的数量为n1,权值和为s1.黑色的数量为n2,权值和为s2.

           设最后变成的数是x.则n1*x-s1=n2*x-s2;

           这样如果n1!=n2我们可以直接把x解出来.

           如果n1==n2,可以发现x合法那么大于x的数都合法.

           我们就可以二分x.

           现在问题变成了如何判断一个x合法.这个可以用最大流来处理.

           源点向白点连容量为(x-a[i][j])的边.

           黑点向汇点连容量为(x-a[i][j])的边.

           白点向黑点连容量为inf的边.

           判断最大流是否等于n1*x-s1即可.

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2000
#define M 20000 
#define inf 1LL<<50
using namespace std;
int T,t,c[N][N],n,m,a[50][50],xi[4]={1,0,-1,0},yi[4]={0,1,0,-1};
int point[N],next[M<<1],cur[N],gap[N],dis[N],pre[N],cnt;
long long sum1,sum2,num1,num2,x,mx;
struct use{
  int st,en;
  long long v;
}e[M<<1];
void add(int x,int y,long long v){
  next[++cnt]=point[x];point[x]=cnt;
  e[cnt].st=x;e[cnt].en=y;e[cnt].v=v;
  next[++cnt]=point[y];point[y]=cnt;
  e[cnt].st=y;e[cnt].en=x;e[cnt].v=0;	
}
long long isap(){
  int i,u(1);long long ans(0);
  memset(gap,0,sizeof(gap));gap[0]=T;
  memset(dis,0,sizeof(dis));
  for (i=1;i<=T;i++) cur[i]=point[i];
  while (dis[1]<T){
    int f(0);
    for (i=cur[u];i;i=next[i])
      if (e[i].v&&dis[e[i].en]+1==dis[u]){f=1;cur[u]=i;break;}
    if (f){
      u=e[i].en;pre[u]=i;
      if (u==T){
        long long mn=inf;
        for (int i=T;i!=1;i=e[pre[i]].st) mn=min(mn,e[pre[i]].v);
        ans+=mn;
        for (int i=T;i!=1;i=e[pre[i]].st) e[pre[i]].v-=mn,e[pre[i]^1].v+=mn;
        u=1;
      }
    }
	else{
	  --gap[dis[u]];if (!gap[dis[u]]) return ans;
	  int mn=T;for (int i=point[u];i;i=next[i]) if (e[i].v) mn=min(mn,dis[e[i].en]);
	  gap[dis[u]=mn+1]++;cur[u]=point[u];if (u!=1) u=e[pre[u]].st;
	} 
  }
  return ans;
}
int cal(int x,int y){
   return (x-1)*m+y;
}
bool check(long long x){
  long long sum(0);
  memset(point,0,sizeof(point));cnt=1;
  for (int i=1;i<=n;i++)
    for (int j=1;j<=m;j++)
      if (c[i][j]){
        add(1,cal(i,j)+1,x-a[i][j]);sum+=x-a[i][j];
        for (int k=0;k<4;k++){
          int xx=i+xi[k],yy=j+yi[k];
          if (xx<1||xx>n||yy<1||yy>m) continue;
          add(cal(i,j)+1,cal(xx,yy)+1,inf);
        }
      }
      else add(cal(i,j)+1,T,x-a[i][j]);
  if (isap()==sum) return true;
  else return false;
}
int main(){
  scanf("%d",&t);
  while (t--){
  	scanf("%d%d",&n,&m);T=n*m+2;
	num1=num2=sum1=sum2=0;mx=0;
  	for (int i=1;i<=n;i++)
  	  for (int j=1;j<=m;j++){
  	     scanf("%d",&a[i][j]);
  	     c[i][j]=(i+j)%2;mx=max(mx,(long long)a[i][j]);
  	     if (c[i][j]==1) num1++,sum1+=a[i][j];
  	     else num2++,sum2+=a[i][j];
  	  }
  	if (num1!=num2){
  	   x=(sum1-sum2)/(num1-num2);
  	   if (check(x))
  	     printf("%lld\n",num1*x-sum1);
  	   else printf("-1\n");
	  }
	else{
	  long long l=mx,r=inf;
	  while (l<=r){
	    long long mid=(l+r)>>1;
	    if (check(mid)) r=mid-1;
	    else l=mid+1;
	  }
	  printf("%lld\n",num1*l-sum1); 
	}
  }	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值