SCOI2012[奇怪的游戏]

2756: [SCOI2012]奇怪的游戏

Time Limit: 40 Sec   Memory Limit: 128 MB
Submit: 2510   Solved: 665
[ 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

Sample Output

2
-1

HINT

【数据范围】 

    对于30%的数据,保证  T<=10,1<=N,M<=8 

对于100%的数据,保证  T<=10,1<=N,M<=40,所有数为正整数且小于1000000000 

 


蒟蒻是一种境界- -

--------------------------------------------------------------------------------------------------------------------------------------------------


真的好想乱搞,看数据范围不算太大- -


but毫无头绪

参考了各种资料+yy的补充,得到网上普遍的一种做法


---考虑最后所有格子都变成了x ,并且把棋盘黑白染色,
黑色的权值为sum1,格子数cnt1   白色的权值sum2,格子数cnt2
所有的都变成了x
则cnt1*x-sum1=cnt2*x-sum2=变化的权值
x=(sum1-sum2)/(cnt1-cnt2)
x需要>=max(s[i][j]) 
然后讨论:
    ①cnt1!=cnt2的时候,解出x的值,check
    ②cnt1==cnt2的时候,如果sum1!=sum2.不成立(代入上述方程无解)
    否则,解有无穷多,找满足题意得最小的解x,二分+check
经过网上题解的洗脑+自己的画图分析,得到这样一个关系式
如果x满足条件,则(cnt1+cnt2)*x-maxflow*2=sum1+sum2 
并且如果x,满足,那么x+1也满足(这种情况下cnt1==cnt2棋盘为
偶数,一定可以分成n/2个小的长方体)
接下来就check好了

关于建边:
将棋盘黑白染色之后,黑色的棋子和S连一条x-ss[i][j]的边,
和四边白色连一条inf的边,白色和T连一条x-ss[i][j]的边



然后就是写最大流的问题了,不知道为什么网上说这道题卡Dinic,不过我写的一般是听说是ISAP的东东,在当前一直更新,发现没被卡掉- -

还有就是long long 的问题,以及inf要大


总结一下:对于棋盘问题(尤其是相邻棋子问题)考虑黑白染色,然后就视情况而定。

这里有一点二分的意思,所以考虑是否往二分上靠(主要看数据估算设计算法的复杂度)

太蒟蒻了- -


#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<cmath>
#define LL long long
using namespace std;	
/*---考虑最后所有格子都变成了x ,并且把棋盘黑白染色,
黑色的权值为sum1,格子数cnt1   白色的权值sum2,格子数cnt2
所有的都变成了x
则cnt1*x-sum1=cnt2*x-sum2=变化的权值
x=(sum1-sum2)/(cnt1-cnt2)
x需要>=max(s[i][j]) 
然后讨论:
    ①cnt1!=cnt2的时候,解出x的值,check
    ②cnt1==cnt2的时候,如果sum1!=sum2.不成立(代入上述方程无解)
    否则,解有无穷多,找满足题意得最小的解x,二分+check
	经过网上题解的洗脑+自己的画图分析,得到这样一个关系式
	如果x满足条件,则(cnt1+cnt2)*x-maxflow*2=sum1+sum2 
	并且如果x,满足,那么x+1也满足(这种情况下cnt1==cnt2棋盘为
	偶数,一定可以分成n/2个小的长方体)
	接下来就check好了
		
关于建边:
将棋盘黑白染色之后,黑色的棋子和S连一条x-ss[i][j]的边,
和四边白色连一条inf的边,白色和T连一条x-ss[i][j]的边 
*/
const LL inf=0x3f3f3f3f3f3f3f3fLL;
LL n,m;
LL ss[50][50];
LL sum1,sum2,cnt1,cnt2;
LL maxz;
LL S,T;
struct node
{
	LL v,cap,next;
}e[2100000+20];
LL head[2000];
LL dis[2000];
LL k;
void add(LL u,LL v,LL cap)
{
	e[k].v=v;
	e[k].cap=cap;
	e[k].next=head[u];
	head[u]=k++;
	
	e[k].v=u;
	e[k].cap=0;
	e[k].next=head[v];
	head[v]=k++;
}
LL dx[]={0,0,1,-1};
LL dy[]={1,-1,0,0};
bool bfs()
{
	queue<LL >q;
	q.push(S);
	memset(dis,-1,sizeof(dis));
	dis[S]=0;
	while(!q.empty())
	{
		LL u=q.front();
		q.pop();
		for(LL i=head[u];i!=-1;i=e[i].next)
		{
			LL v=e[i].v;
			if(dis[v]==-1&&e[i].cap>0)
			{
				dis[v]=dis[u]+1;
				if(v==T)return true;
				q.push(v);
			}
		}
	}
	return false;
}
LL dfs(LL u,LL low)
{
	if(u==T||low==0)return low;
	LL ret=0;
	for(LL i=head[u];i!=-1;i=e[i].next)
	{
		LL v=e[i].v;
		if(dis[v]==dis[u]+1)
		{
			LL f=dfs(v,min(low,e[i].cap));
			e[i].cap-=f;
			e[i^1].cap+=f;
			low-=f;
			ret+=f;
			if(low==0)return ret;
		}
	}
	dis[u]=-1;
	return ret;
}
LL work()
{
	LL ans=0;
	while(bfs())
	{
		LL x;
		if(x=dfs(S,inf))ans+=x;
	}
	return ans;
}
bool check(LL x)
{
	memset(head,-1,sizeof(head));
	for(LL i=1;i<=n;i++)
	{
		for(LL j=1;j<=m;j++)
		{
			LL id=(i-1)*m+j;
			if((i+j)&1)//白色 
			{
				add(id,T,x-ss[i][j]);
			}
			else//黑色 
			{
				add(S,id,x-ss[i][j]);
				for(LL l=0;l<4;l++)
				{
					LL nx=i+dx[l];
					LL ny=j+dy[l];
					if(nx>=1&&nx<=n&&ny>=1&&ny<=m)
					{
						LL nid=(nx-1)*m+ny;
						add(id,nid,inf);
					}
				}
			} 
		}
	}
	
	//建边完成 
	return (cnt1+cnt2)*x-work()*2==sum1+sum2;
}
int main()
{
	LL t;
	scanf("%lld",&t);
	while(t--)
	{
		sum1=sum2=cnt1=cnt2=0;
		maxz=0;
		k=0;
		scanf("%lld%lld",&n,&m);
		S=0,T=n*m+1;
		for(LL i=1;i<=n;i++)
		{
			for(LL j=1;j<=m;j++)
			{
				scanf("%lld",&ss[i][j]);
				maxz=max(maxz,ss[i][j]);
				if((i+j)&1)//白棋 
				{
					sum2+=ss[i][j];
					cnt2++;
				}
				else
				{
					sum1+=ss[i][j];
					cnt1++;
				}
			}
		}
		if(cnt1==cnt2)
		{
			if(sum1!=sum2)printf("-1\n");
			else//二分+check 
			{
				LL l=maxz,r=inf;
				while(l<r)
				{
					LL mid=(l+r)>>1;
					if(check(mid))r=mid;
					else l=mid+1;
				}
				printf("%lld\n",((cnt1+cnt2)*l-(sum1+sum2))/2);
			}
		}
		else
		{
			LL x=(sum1-sum2)/(cnt1-cnt2);
			if(x<maxz||!check(x))printf("-1\n");
			else printf("%lld\n",((cnt1+cnt2)*x-(sum1+sum2))/2);
		}
	}	
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值