bzoj 1565: [NOI2009]植物大战僵尸 (最大权闭合子图+tarjan+拓扑序)

91 篇文章 0 订阅
24 篇文章 0 订阅

1565: [NOI2009]植物大战僵尸

Time Limit: 10 Sec   Memory Limit: 64 MB
Submit: 2345   Solved: 1081
[ Submit][ Status][ Discuss]

Description

Input

Output

仅包含一个整数,表示可以获得的最大能源收入。注意,你也可以选择不进行任何攻击,这样能源收入为0。

Sample Input

3 2
10 0
20 0
-10 0
-5 1 0 0
100 1 2 1
100 0

Sample Output

25

HINT

在样例中, 植物P1,1可以攻击位置(0,0), P2, 0可以攻击位置(2,1)。 
一个方案为,首先进攻P1,1, P0,1,此时可以攻击P0,0 。共得到能源收益为(-5)+20+10 = 25。注意, 位置(2,1)被植物P2,0保护,所以无法攻击第2行中的任何植物。 
【大致数据规模】
约20%的数据满足1 ≤ N, M ≤ 5;
约40%的数据满足1 ≤ N, M ≤ 10;
约100%的数据满足1 ≤ N ≤ 20,1 ≤ M ≤ 30,-10000 ≤ Score ≤ 10000 。

Source

[ Submit][ Status][ Discuss]

题解:最大权闭合子图+tarjan+拓扑序

因为有些位置会受到植物的保护,所以只有僵尸将保护他的所有植物全部吃掉后才能得到这个位置的价值。我们可以从受保护的位置向保护他的位置连边。因为是从右向左发起攻击,所以只有右边的植物被吃掉才能吃到左边的植物,所以我们对于每一行从左向右连边。我们之所以这么建图是因为边的指向表示依赖关系,也就是这个点选了,那么他连出的点一定选了,符合最大权闭合子图的模型,也符合题目中的要求。这样进行转换之后我们发现其实有些位置是无论如何都攻击不到的,那么我们的目标是转化成最大权闭合子图的模型,所以我们进行tarjan缩点,如果一个连通块内有不止一个点的话,那么这个连通块内的点一定都无法得到,同样有边连向的这个连通块的点也无法得到,我们可以tarjan后反向建边,按照拓扑序,进行更新。

最后网络流建图的时候只保留能到达的点和边,然后按照最大权闭合子图的方式求解即可。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<queue>
#define N 800003
#define inf 1000000000
using namespace std;
int tot,next[N],point[N],v[N],remain[N],ck[N];
int deep[N],num[N],cur[N],last[N];
int tt,head[N],u[N],nxt[N],n,m,s,t,mark[N],mp[N];
int ins[1000],low[1000],dfsn[1000],size[1000],st[N],top,sz,cnt,belong[1000],id[53][53],val[1000];
void add(int x,int y,int z)
{
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z;
	tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0;
	//cout<<x<<" "<<y<<" "<<z<<endl;
}
void build(int x,int y)
{
	tt++; nxt[tt]=head[x]; head[x]=tt; u[tt]=y;
	//cout<<x<<" "<<y<<endl;
}
int addflow(int s,int t)
{
	int now=t; int ans=inf;
	while (now!=s) {
		ans=min(ans,remain[last[now]]);
		now=v[last[now]^1];
	}
	now=t;
	while (now!=s) {
		remain[last[now]]-=ans;
		remain[last[now]^1]+=ans;
		now=v[last[now]^1];
	}
	return ans;
}
void bfs(int s,int t)
{
	for (int i=s;i<=t;i++) deep[i]=t;
	queue<int> p; p.push(t); deep[t]=0;
	while (!p.empty()){
		int now=p.front(); p.pop();
		for (int i=point[now];i!=-1;i=next[i])
		 if (deep[v[i]]==t&&remain[i^1]) 
		  deep[v[i]]=deep[now]+1,p.push(v[i]);
	}
}
int isap(int s,int t)
{
	int now=s; int ans=0;
	bfs(s,t);
	for (int i=s;i<=t;i++) num[deep[i]]++;
	for (int i=s;i<=t;i++) cur[i]=point[i];
	while (deep[s]<t) {
		if (now==t) {
			ans+=addflow(s,t);
			now=s;
		}
		bool pd=false;
		for (int i=cur[now];i!=-1;i=next[i])
		 if (deep[v[i]]+1==deep[now]&&remain[i]) {
		 	cur[now]=i;
		 	last[v[i]]=i;
		 	now=v[i];
		 	pd=true;
		 	break;
		 }
		if (!pd) {
			int minn=t;
			for (int i=point[now];i!=-1;i=next[i])
			 if (remain[i]) minn=min(minn,deep[v[i]]);
			if (!--num[deep[now]]) break;
			num[deep[now]=minn+1]++;
			cur[now]=point[now];
			if (now!=s) now=v[last[now]^1];
		}
	}
	return ans;
}
void tarjan(int x)
{
	st[++top]=x; ins[x]=1;
	dfsn[x]=low[x]=++sz;
	for (int i=head[x];i;i=nxt[i]){
		int j=u[i];
		if (!dfsn[j]) tarjan(j),low[x]=min(low[x],low[j]);
		else if (ins[j]) low[x]=min(low[x],dfsn[j]);
	}
	if (low[x]==dfsn[x]) {
		++cnt; int j;
		do{
			j=st[top--];
			belong[j]=cnt;
			mp[cnt]=j;
			size[cnt]++;
			ins[j]=0;
		}while (j!=x);
	}
}
void rebuild(int x,int y)
{
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; ins[y]++;
	//cout<<x<<" "<<y<<endl;
}
void solve()
{
	memset(ins,0,sizeof(ins));
	for (int i=1;i<=n*m;i++) 
	 for (int j=head[i];j;j=nxt[j]) 
	  if (belong[i]!=belong[u[j]])
	   rebuild(belong[u[j]],belong[i]);
	queue<int> p;
	for (int i=1;i<=cnt;i++) 
	 if (!ins[i]) p.push(i);
	while (!p.empty()) {
		int now=p.front(); p.pop();
		for (int i=point[now];i;i=next[i]){
			ins[v[i]]--;
			if (ck[mp[now]]) ck[mp[v[i]]]=1;
			if (!ins[v[i]]) p.push(v[i]);
		}
	}
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	 for (int j=1;j<=m;j++) id[i][j]=++cnt;
	for (int i=1;i<=n;i++) 
	 for (int j=1;j<=m;j++) {
	 	scanf("%d",&val[id[i][j]]);
	 	int k; scanf("%d",&k);
	 	for (int l=1;l<=k;l++) {
	 		int x,y; scanf("%d%d",&x,&y);
	 		x++; y++; build(id[x][y],id[i][j]);
		 }
	 }
	for (int i=1;i<=n;i++) 
	 for (int j=2;j<=m;j++)
	  build(id[i][j-1],id[i][j]);
	cnt=0;  s=1; t=n*m+2;
	for (int i=1;i<=n*m;i++)
	 if (!dfsn[i]) tarjan(i);
	for (int i=1;i<=n*m;i++)
	 if (size[belong[i]]!=1) ck[i]=1;
	solve();
 	tot=-1;
	memset(point,-1,sizeof(point));
	for (int i=1;i<=n*m;i++) 
	 for (int j=head[i];j;j=nxt[j]) 
	   if (!ck[i]&&!ck[u[j]]) add(i+1,u[j]+1,inf);
	int sum=0;
	//for (int i=1;i<=n*m;i++) cout<<ck[i]<<" ";
	//cout<<endl;
	for (int i=1;i<=n*m;i++)
	 if (!ck[i]){
	  if (val[i]>0) add(s,i+1,val[i]),sum+=val[i];
	  else add(i+1,t,-val[i]);
    }
	int flow=isap(s,t); 
	printf("%d\n",sum-flow);
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值