【二分+最大流】[SCOI2012]奇怪的游戏 BZOJ2756

[SCOI2012]奇怪的游戏

Time Limit: 40 Sec  Memory Limit:128 MB


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







说实话,真看不出来是网络流,后来HNY说二分答案我才反应过来可以二分最后每个格子最后的值,然后验证

但是格子总数是奇数的时候要单独考虑

(下面引用njlcazl   http://blog.csdn.net/njlcazl/article/details/8732646)

由于每次操作都是相邻的两个,所以可以考虑将棋盘黑白染色,这样我们可以对黑色的格子和白色的格子单独考虑。

设黑色格子个数为cnt1,总和为sum1,白色格子个数为cnt2,总和为sum2,最终所有格子都变成了x,则很容易写出下列的关系式:

     cnt1 *x - sum1 = cnt2 * x - sum2

=> x * (cnt1 - cnt2) = sum1 - sum2

=> x = (sum1 - sum2) / (cnt1 - cnt2)

现在讨论cnt1 与 cnt2的情况:

1、若cnt1 = cnt2,则说明在sum1 = sum2的情况下,若x可行,则x+1同样可以,这样我们可以通过二分的方法找出最小的满足条件的x,求出最小步数;若sum1 != sum2则无解。

2、若cnt1!= cnt2,则我们解出了一个x

这个x必须要满足三个条件:

(1)x必须为整数

(2)x不小于所有格子的最大值

(3)x必须要通过检验

如果x满足这三个条件,则我们可以解出最小步数,否则无解。

那么我们怎样检验x是否可行呢,这个需要通过网络流来验证。

设map[i][j]为格子对应的数字,对于每个黑色的格子,从源点连一条边,容量为x - map[i][j],并且对它周围的四个白色格子连一条边容量为inf;对于每个白色格子,向汇点连一条边,容量为x - map[i][j]

设最大流为maxflow,若maxflow = (n * m - sum1 - sum2) >> 1的话,说明存在方案可以使所有数变成x,同时最小步数minstep = (n * m - sum1 - sum2) >> 1

注意到格子的数可能很大,所以要使用long long类型

这样就解决了本题。

(引用结束)


然后,上面虽然是cnt1-cnt2,但是我们可以知道在奇数个的时候cnt1-cnt2一定等于1



然后网上都说这一题卡递归的sap或递归Dinic,必须写非递归的,但是HNY大神说他递归的也过了,然后我也写递归。。。。。调了两天,还是 T 。。。。。


结果今天他猛然记起他把递归sap优化了一下  http://blog.csdn.net/jiangzh7/article/details/8892588   ,时间效率优于非递归!!!

然后果断把他A了


测评情况(BZOJ)

在本机一直WA和T,本来就快改成非递归了,HNY大神说可以优化,然后就很兴奋的A了~



C++ AC Code

/*http://blog.csdn.net/jiangzh7
By Jiangzh*/
#include<cstdio>
#include<cstring>
#define min(a,b) ((a)<(b)?(a):(b))
#define max(a,b) ((a)>(b)?(a):(b))
typedef long long LL;
const int N=40+10;
const int NN=50*50;
const int dx[]={0,0,-1,1};
const int dy[]={1,-1,0,0};
const int inf=0x3f3f3f3f;
const LL  infll=0x3f3f3f3f3f3f3f3fLL;

int n,m;
int map[N][N],color[N][N];
struct EG{int y;LL flow;int next;}edge[NN*10];
int head[NN],L;
int v[NN],h[NN];
int S,T;
LL num[2],sum[2],maxp=0;

void read()
{
	scanf("%d%d",&n,&m);
	int cc=1;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&map[i][j]);
			color[i][j]=!color[i][j-1];
			if(j==1) color[i][j]=(cc=!cc);
			num[color[i][j]]++;
			sum[color[i][j]]+=map[i][j];
			maxp=max(maxp,map[i][j]);
		}
}

inline int get(int x,int y) { return (x-1)*m+y; }

LL sap(int x,LL flow)
{
	if(x==T) return flow;
	LL res=0;
	for(int i=head[x];i!=-1;i=edge[i].next)
		if(edge[i].flow && h[x]==h[edge[i].y]+1)
		{
			LL t=sap(edge[i].y,min(edge[i].flow,flow-res));
			edge[i].flow-=t;
			edge[i^1].flow+=t;
			if((res+=t)>=flow) return res;
			if(h[S]>T) return res;
		}
	if((--v[h[x]])==0) h[S]=T+1;
	++v[++h[x]];
	return res;
}

void inlink(int x,int y,LL z)
{
	edge[L]=(EG){y,z,head[x]};
	head[x]=L++;
	edge[L]=(EG){x,0,head[y]};
	head[y]=L++;
}

bool check(LL final)
{
	memset(edge,0,sizeof(edge));
	memset(head,-1,sizeof(head));L=0;
	memset(v,0,sizeof(v));
	memset(h,0,sizeof(h));
	S=0;T=get(n,m)+1;
	LL total=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			LL tmp=final-map[i][j];
			if(color[i][j]==0) inlink(S,get(i,j),tmp);
			else inlink(get(i,j),T,tmp);
			total+=tmp;
		}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int k=0;k<4;k++)
			{
				int x=i+dx[k],y=j+dy[k];
				if(x<1||x>n||y<1||y>m) continue;
				if (color[i][j]==0) inlink(get(i,j),get(x,y),infll);
			}
	v[0]=T+1;
	LL maxflow=0;
	while(h[S]<T+1) maxflow+=sap(S,infll);
	return total==(maxflow*2);
}

inline LL getans(LL x)
{
	return ((LL)n*m*x-sum[0]-sum[1])/2;
}

void work()
{
	if((n*m)%2==0)
	{
		LL L=1,R=infll/1000,M;
		int ans=-1;
		while(L<=R)
		{
			M=L+((R-L)>>1);
			if(check(M)) R=M-1,ans=M;
			else L=M+1;
		}
		if(ans==-1) printf("%d\n",ans);
		else printf("%lld\n",getans(ans));
	}
	else{
		bool flag=0;
		LL x=(sum[0]-sum[1]);
		if(x>=maxp && check(x)) flag=1;
		if(flag) printf("%lld\n",getans(x));
		else printf("-1\n");
	}
}

int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	int T; scanf("%d",&T);
	while(T--)
	{
		memset(map,0,sizeof(map));
		memset(color,0,sizeof(color));
		sum[0]=sum[1]=num[0]=num[1]=maxp=0;
		read();
		work();
	}
	return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值