bzoj 1565:植物大战僵尸(最大权闭合子图 + tarjan判断每个点是否在环内)

题目大意:有n * m 的矩阵,每一个格子有一个植物,每个植物有一个能量值(可正可负)吃掉这个格子的植物就可以得到这个植物的能量值,有的植物可以保护某些格子的植物,如果你要吃掉某个植物,必须先吃掉保护它的植物。僵尸只能从最右边开始吃,吃一个植物之前必须先吃掉在它右边的植物,僵尸也可以换行吃,换行后还是得从最右边开始吃起,如果你是操作僵尸的玩家,你能获得的最大能力值的多少?

(正向指的是按吃的先后顺序方向建边,例如吃a必须先吃c,建c -> a,反向则建a -> c)

分析:分析一下那些限制条件,吃掉一个植物之前必须先吃掉其它的植物。这符合闭合子图的定义,只要将边反过来建,那么这个问题就完全等价于求最大权闭合子图。然后套用经典的网络流模型求解。

建图过程大概都懂
要注意有可能存在环,环内的点是不可达的。

如果边是正着建的,那么t所在的点集是一个解,因为不存在一条边 &lt; u , v &gt; &lt;u,v&gt; <u,v> 使得 u ∈ S , v ∈ T u \in S,v \in T uSvT,T集合对应的点集中每一个点的入度都来自于T集中的点。那么环这个问题,可以用拓扑排序解决,正向建边的话,在环后面的点是不可能吃到的,拓扑排序完一遍后那些入度不为0的点就是不可达的点,和s,t建边时只建可达的点,点权为正的点连S,容量为点权,点权为负的点连T,容量为点权的绝对值。

如果反过来建,那么s所在的点集是一个解,如果用拓扑排序,环后面的点会判断为吃不到,但反向建边环后面的点是可以吃到的(建边从左往右建,但吃是从右往左吃,如果左往右有环,环后面的可以吃),不可以吃的只有环里面的点,可以用tarjan算法把环弄出来,判断每个点是否在环内,如果不在环内就和s,t连边。

两种建边方法的不同主要在于:第一种建图,环后面的点不能吃,答案点集内每个点的入度必须来自这个点集。第二种建图是环后面的可以吃,答案点集内每个点的出度必须到这个点集。

第二种建图方式更符合传统的最大权闭合子图定义,贴出第二种代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
const int maxm = 1e6 + 10;
const int inf = 0x3f3f3f3f;
int a[3000],n,m;
struct ss{
	int u,v,w,nxt;
}edg[maxm];
int head[3000],cnt,d[3000];
int top,sta[maxn],low[maxn],dfn[maxn],sz,in[maxn],belong[maxn],num[maxn],lab;
bool is[maxn];
void init() {
	cnt = 0;
	memset(low,0,sizeof low);
	memset(num,0,sizeof num);
	memset(in,0,sizeof in);
	memset(belong,0,sizeof belong);
	memset(dfn,0,sizeof dfn);
	top = sz = lab = 0;
	memset(head,-1,sizeof head);
}
void add(int u,int v,int w) {
	edg[cnt].u = u;
	edg[cnt].v = v;
	edg[cnt].w = w;
	edg[cnt].nxt = head[u];
	head[u] = cnt++;
}
bool bfs(int s,int t) {
	queue<int> q;
	memset(d,0,sizeof d);
	q.push(s);d[s] = 1;
	while(!q.empty()) {
		int top = q.front();
		q.pop();
		for(int i = head[top]; i + 1; i = edg[i].nxt) {
			int v = edg[i].v,w = edg[i].w;
			if(w && !d[v]) {
				d[v] = d[top] + 1;
				q.push(v);
			}
		}
	}
	return d[t] > 0;
}
int dfs(int s,int t,int inflow) {
	if(s == t || !inflow) return inflow;
	int flow = 0;
	for(int i = head[s]; i + 1; i = edg[i].nxt) {
		int v = edg[i].v,w = edg[i].w;
		if(w && d[v] == d[s] + 1) {
			int x = dfs(v,t,min(w,inflow));
			inflow -= x;flow += x;
			edg[i].w -= x;edg[i ^ 1].w += x;
			if(!inflow) break;
		}
	}
	if(flow == 0) d[s] = -2;
	return flow;
}
int dinic(int s,int t) {
	int ans = 0;
	while(bfs(s,t)) ans += dfs(s,t,inf);
	return ans;
}
void tarjan(int u,int fa) {
	dfn[u] = low[u] = ++sz;
	sta[++top] = u;
	in[u] = 1;
	for(int i = head[u]; i + 1; i = edg[i].nxt) {
		int v = edg[i].v,w = edg[i].w;
		if(w) {
			if(!dfn[v]) {
				tarjan(v,u);
				low[u] = min(low[u],low[v]); 
			}
			else if(in[v])
				low[u] = min(low[u],dfn[v]);
		}
	}
	if(dfn[u] == low[u]) {
		lab++;
		do {
			belong[sta[top]] = lab;
			in[sta[top]] = 0;
			num[lab]++;
		}while(sta[top--] != u);
	}
}
int main() {
	init();
	scanf("%d%d",&n,&m);
	int s = 0,t = n * m + 1;
	for(int i = 1; i <= n * m; i++) {
		int tot,x,y;
		scanf("%d",&a[i]);
		scanf("%d",&tot);
		for(int j = 1; j <= tot; j++) {
			scanf("%d%d",&x,&y);
			x++;y++;
			int p = (x - 1) * m + y;
			add(p,i,inf);
			add(i,p,0);
		}
	}
	for(int i = 1; i <= n; i++)
		for(int j = 1; j < m; j++) {
			add((i - 1) * m + j,(i - 1) * m + j + 1,inf);
			add((i - 1) * m + j + 1,(i - 1) * m + j,0);
		}
	for(int i = 1; i <= n * m; i++)
		if(!dfn[i]) tarjan(i,-1);
	int sum = 0;
	for(int i = 1; i <= n * m; i++) {
		if(num[belong[i]] == 1) {
			if(a[i] > 0) add(s,i,a[i]),add(i,s,0),sum += a[i];
			else if(a[i] < 0) add(i,t,-a[i]),add(t,i,0);
		}
	}
	sum -= dinic(s,t);
	printf("%d\n",sum);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值