snoi省选模拟赛 day3t2 游戏

       GFS CLJ在玩这样一个游戏:

      有一个N*M的矩阵,每个格子代表一个人。他可以选择杀死一个人,或者让一个人活着。如果处在(i, j)位置的人被杀,得到B[i][j]的收益,否则得到W[i][j]的收益。

       另外还有Q个额外任务,每个任务的形式是这样的:

      如果以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵表示的人全部活着/全部被杀,可以得到c的额外收益。其中,具体是要求活着还是被杀是给定的。

      注意:并不是所有的任务都一定要被完成!CLJ可以自主选择一些任务去完成。

       现在他想知道,怎样做才能使得收益最大。

       

       考场上推了三个小时,终于推出建图方案,还是机智的我早在博客里写了将整体划分为两个集合的问题可以用最小割来做。

       首先,我们很明显的能够看出,题目要将每个人划分为生与死的两个集合。如果我们从源点向每个人连一条边权为活着的收益,再从人向汇点连一条为死了的收益,那么整个图的最小割就是没有额外任务的答案了。因为我们对于一个人选择且仅会选择生与死的一条边,因为如果都选的话,割的大小会变大,则不是最小割了,如果都不选,则源点和汇点联通。

       接着,对于每个子问题,我们可以对它单开一个点,如果是全生,则从原点连一条容量为这个子任务的边,再将这个点连向它覆盖的矩形的每一个点,容量为无穷大。如果为全死,则从该店向汇点连一条容量为这个子任务的边,再将它覆盖的矩形的每一个点连向它,容量为无穷大。对于全生来说,为了成为一个最小割,肯定不会选无穷大的边,如果要选这个子任务而使得图不联通,那么必须选择它链接矩形的点全部状态为死,才能得到这个答案,如下图。


        这样建图就可以得到80分了,对于一部分数据q*m*n的建边复杂度难以接受,所以我们就可以考虑通过倍增来优化建图。我们用st[0/1][x][y][i][j]表示(生/死)以x,y为左上角向下(2^i)向右(2^j)的点集,那么类似st表的,我们可以用最多4个矩形,来完整覆盖询问的矩形。那么由于我们只需要维护图的拓扑关系,我们就可以处理出一些大的中间点,再利用这些中间点来中转流量即可。如对于生来说,可以对于每个处理出来的大的矩形,来向其包含的小的矩形连一条容量为inf的边,那么依旧能维护上文的关系,且边数为n*m*logn*logm,所以预处理出来之后,对于每个询问,再直接向大矩形连边即可。

        下附AC代码。

#include<iostream>
#include<stdio.h>
#include<string.h> 
#include<algorithm>
#include<queue>
#include<math.h>
#define maxn 1005
using namespace std;
int n,m,q,tot=1,cnt;
int mylog[maxn*maxn];
int head[maxn*maxn],to[maxn*maxn*5],nex[maxn*maxn*5],val[maxn*maxn*5];
int b[maxn][maxn],w[maxn][maxn];
int st[2][105][105][21][21];
void add(int x,int y,int z)
{
	to[++tot]=y;val[tot]=z;nex[tot]=head[x];head[x]=tot;
	to[++tot]=x;val[tot]=0;nex[tot]=head[y];head[y]=tot;
}
int getpos(int i,int j)
{
	return (i-1)*m+j;
}
int level[maxn*maxn],iter[maxn*maxn];
void bfs(int now)
{
	queue<int>q;
	for(int i=1;i<=n;i++) level[i]=-1;
	level[now]=0;q.push(now);
	while(!q.empty())
	{
		now=q.front();q.pop();
		for(int i=head[now];i;i=nex[i])
		if(val[i]>0 && level[to[i]]==-1)
		level[to[i]]=level[now]+1,q.push(to[i]);
	}
}
int dfs(int u,int v,int flow)
{
	if(u==v) return flow;
	for(int &i=iter[u];i;i=nex[i])
	{
		if(val[i]>0 && level[to[i]]>level[u])
		{
			int res=dfs(to[i],v,min(val[i],flow));
			if(res>0)
			{
				val[i]-=res;
				val[i^1]+=res;
				return res;
			}
		}
	}
	return 0;
}
int dinic(int u,int v)
{
	int ans=0;
	while(1)
	{
		bfs(u);
		if(level[v]==-1) return ans;
		for(int i=1;i<=n;i++) iter[i]=head[i];
		int f;
		while((f=dfs(u,v,123456789))>0)
		ans+=f;
	}
}
int main()
{
//	freopen("game.in","r",stdin);
//	freopen("game.out","w",stdout);
	mylog[2]=1;
	for(int i=3;i<maxn*maxn;i++)
	mylog[i]=mylog[i>>1]+1;
	scanf("%d%d%d",&n,&m,&q);
	tot=1;
	int ans=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&b[i][j]);
			ans+=b[i][j];
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&w[i][j]);
			ans+=w[i][j];
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cnt++;
			st[0][i][j][0][0]=st[1][i][j][0][0]=cnt;
		}
	}
	
	for(int i=0;(1<<i)<=n;i++)
	{
		for(int j=0;(1<<j)<=m;j++)
		if(i+j)
		{
			for(int x=1;x+(1<<i)-1<=n;x++)
			{
				for(int y=1;y+(1<<j)-1<=m;y++)
				{
					st[0][x][y][i][j]=++cnt;
					st[1][x][y][i][j]=++cnt;
				}
			}
		}
	}
	
	int s=cnt+q+1,e=cnt+q+2;
	
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			add(s,getpos(i,j),w[i][j]);
			add(getpos(i,j),e,b[i][j]);
		}
	}
	
	for(int i=0;(1<<i)<=n;i++)
	{
		for(int j=0;(1<<j)<=m;j++)
		if(i+j)
		{
			for(int x=1;x+(1<<i)-1<=n;x++)
			{
				for(int y=1;y+(1<<j)-1<=m;y++)
				{
					if (i && j) 
					add(st[0][x][y][i][j],st[0][x+(1<<i)-1][y+(1<<j)-1][max(0,i-1)][max(0,j-1)],1234567890);
					if (j) 
					add(st[0][x][y][i][j],st[0][x][y+(1<<j)-1][max(0,i-1)][max(0,j-1)],1234567890);
					if (i) 
					add(st[0][x][y][i][j],st[0][x+(1<<i)-1][y][max(0,i-1)][max(0,j-1)],1234567890);
					add(st[0][x][y][i][j],st[0][x][y][max(0,i-1)][max(0,j-1)],1234567890);
				
					if (i && j) 
					add(st[1][x+(1<<i)-1][y+(1<<j)-1][max(0,i-1)][max(0,j-1)],st[1][x][y][i][j],1234567890);
					if (j) 
					add(st[1][x][y+(1<<j)-1][max(0,i-1)][max(0,j-1)],st[1][x][y][i][j],1234567890);
					if (i) 
					add(st[1][x+(1<<i)-1][y][max(0,i-1)][max(0,j-1)],st[1][x][y][i][j],1234567890);
					add(st[1][x][y][max(0,i-1)][max(0,j-1)],st[1][x][y][i][j],1234567890);
				}
			}
		}
	}
	for(int i=1;i<=q;i++)
	{
		int x1,y1,x2,y2,op,c;
		scanf("%d%d%d%d%d%d",&x1,&y1,&x2,&y2,&op,&c);
		ans+=c;
		if(op==0) add(s,cnt+i,c);
		else add(cnt+i,e,c);
		int len1=mylog[x2-x1+1],len2=mylog[y2-y1+1];
		if(op==0)
		{
			add(cnt+i,st[0][x1][y1][len1][len2],1234567890);
			add(cnt+i,st[0][x2-(1<<len1)+1][y1][len1][len2],1234567890);
			add(cnt+i,st[0][x1][y2-(1<<len2)+1][len1][len2],1234567890);
			add(cnt+i,st[0][x2-(1<<len1)+1][y2-(1<<len2)+1][len1][len2],1234567890);
		}
		else
		{
			add(st[1][x1][y1][len1][len2],cnt+i,1234567890);
			add(st[1][x2-(1<<len1)+1][y1][len1][len2],cnt+i,1234567890);
			add(st[1][x1][y2-(1<<len2)+1][len1][len2],cnt+i,1234567890);
			add(st[1][x2-(1<<len1)+1][y2-(1<<len2)+1][len1][len2],cnt+i,1234567890);
		}
	}
	n=e;
	printf("%d\n",ans-dinic(s,e));
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值