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

15 篇文章 0 订阅
13 篇文章 0 订阅

这道题初看也许会感觉无法下手,由于每次操作都是相邻的两个,所以可以考虑将棋盘黑白染色,这样我们可以对黑色的格子和白色的格子单独考虑。

设黑色格子个数为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 * x - sum1 - sum2) >> 1的话,说明存在方案可以使所有数变成x,同时最小步数minstep = (n * m * x - sum1 - sum2) >> 1

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

这样就解决了本题

代码

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 50;
const int maxpoint = 3000;
const int maxm = 100000;
const long long inf = 0x3f3f3f3f3f3f3f3fll;
struct Edge
{
	int pos;
	long long c;
	int next;
}E[maxm];
int map[maxn][maxn],head[maxpoint];
int dis[maxpoint],gap[maxpoint],cur[maxpoint],pre[maxpoint];
int T,n,m;
int NE,s,t,nodenum;
long long maxw,cnt1,sum1,cnt2,sum2,com;
void init()
{
	freopen("bzoj2756.in","r",stdin);
	freopen("bzoj2756.out","w",stdout);
}

inline int max(long long a,long long b)
{
	return a > b ? a : b;
}

inline void checkmin(long long &a,long long b)
{
	if(a == -1 || a > b)a = b;
}

inline long long gettime(long long x)
{
	return (x * n * m - sum1 - sum2) >> 1;
}

void insert(int u,int v,long long c)
{
	E[NE].c = c;E[NE].pos = v;
	E[NE].next = head[u];head[u] = NE++;
	E[NE].c = 0;E[NE].pos = u;
	E[NE].next = head[v];head[v] = NE++;
}

long long sap()
{
	memset(dis,0,sizeof(dis));
	memset(gap,0,sizeof(gap));
	for(int i = s;i <= t;i++)cur[i] = head[i];
	int u = pre[s] = s;
	long long  maxflow = 0,aug = -1;
	gap[0] = nodenum;
	while(dis[s] < nodenum)
	{
loop:  for(int &i = cur[u];i != -1;i = E[i].next)
	   {
		   int v = E[i].pos;
		   if(E[i].c && dis[u] == dis[v] + 1)
		   {
			   checkmin(aug,E[i].c);
			   pre[v] = u;
			   u = v;
			   if(v == t)
			   {
				   maxflow += aug;
				   for(u = pre[u];v != s;v = u,u = pre[u])
				   {
				       E[cur[u]].c -= aug;
					   E[cur[u]^1].c += aug;
				   }
				   aug = -1;
			   }
			   goto loop;
		   }
	   }
	   int mind = nodenum;
	   for(int i = head[u];i != -1;i = E[i].next)
	   {
		   int v = E[i].pos;
		   if(E[i].c && (mind > dis[v]))
		   {
				cur[u] = i;
				mind = dis[v];
		   }
	   }
	   if(--(gap[dis[u]]) == 0)break;
	   gap[dis[u] = mind + 1]++;
	   u = pre[u];
	}
	return maxflow;
}

bool check(long long x)
{
	memset(E,0,sizeof(E));
	memset(head,-1,sizeof(head));
	NE = 0,s = 0,t = n * m + 1;
	nodenum = t + 1;
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= m;j++)
		{
			if((i + j) & 1)
			{
				insert(s,(i - 1) * m + j,x - map[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 - map[i][j]);
		}
	}
	return ((sap() << 1) == (x * n * m - sum1 - sum2));
}

void solve()
{
	if(cnt1 != cnt2)
	{
		if((sum1 - sum2) % (cnt1 - cnt2) != 0)printf("-1\n");
		else
		{
			long long x = (sum1 - sum2) / (cnt1 - cnt2);
			if(x < maxw)
			{
				printf("-1\n");
				return;
			}
			if(check(x))printf("%lld\n",gettime(x));
			else printf("-1\n");
		}
	}
	else
	{
		long long l = maxw,r = inf;
		while(l < r)
		{
			long long m = (l + r) >> 1;
			if(check(m))r = m;
			else l = m + 1;
		}
		if(check(r))printf("%lld\n",gettime(r));
		else printf("-1\n");
	}
}

void readdata()
{
	scanf("%d",&T);
	while(T--)
	{
		memset(map,0,sizeof(map));
		maxw = 0,sum1 = 0,cnt1 = 0,sum2 = 0,cnt2 = 0;
		scanf("%d%d",&n,&m);
		for(int i = 1;i <= n;i++)
		{
			for(int j = 1;j <= m;j++)
			{
				scanf("%d",&map[i][j]);
				if((i + j) & 1)cnt1++,sum1 += map[i][j];
				else cnt2++,sum2 += map[i][j];
				maxw = max(maxw,map[i][j]);
			}
		}
		solve();
	}
}

int main()
{
	init();
	readdata();
	return 0;
}



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值