P2805 [NOI2009] 植物大战僵尸-题解

P2805 [NOI2009] 植物大战僵尸

题目

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

链接

https://www.luogu.com.cn/problem/P2805

题意


植物大战僵尸,植物可以一击杀死僵尸,僵尸进入植物的攻击位置是就会死亡,即使该位置上有植物,每个植物拥有一个Score,该Score可以是正数也可以是负数,代表你击杀植物得到的能量。问如何安排僵尸的攻击顺序,来获得最大的能量收入


思路

最小割+拓扑排序

  • 根据题意,设一个植物小A,右边的植物小B,如果小A的攻击位置是小B的位置,已知僵尸必须先吃小B,也就是说小B保护小A,又因为僵尸一进入植物的攻击位置即死,所以小A保护小B,也就是说它们互相保护。僵尸无法攻击他们。首先右边的植物 V 向左边的植物 U 连一条边,并且 U 向它攻击的位置 P 连一条边,表示U保护P。


例如,该图形成一个闭环僵尸无法攻击
在这里插入图片描述

  • 使用拓扑排序找环去除环上的点和被环保护的点,因为建边是从右向左建一条边连着,那么被环保护的点的度数>=2,环上的点也是>=2,所以拓扑的时候将度数为0的边放入队列中,每次只将度数为0的点放入队列中即可,这样在队列里面的就是可以建图的点。

  • 如果你想吃掉植物A,就得把植物A的右边所有的植物以及所有保护植物A的植物都吃掉,与最大权闭合子图的对于每个点,从它出发,能走到的所有点都属于一个闭合子图的性质相同。

在这里插入图片描述

  • 根据闭合子图的性质,我们需要将原来的边转置流量为 +∞\infty,并且建立虚拟源/汇点,从源点向它每一个权值为正的点连一条流量为权值的边,从每一个权值为负的点向汇点连一条流量为权值的绝对值的边,
  • 最后用∑(score>0)−dinic()\sum (score>0)- dinic()(score>0)dinic(),也就是 用正权点的权值之和减去最大流即可

代码

C++

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ps push
#define pb push_back
#define PII pair<int,int>
#define mm memset
#define endl "\n"

const int N = 1000 , M = (100000+N)*5+15 , INF = 1e8;

int n,m,S,T;
int h[N] , f[M] , e[M] , ne[M] , idx = 0;
int q[N],cur[N],d[N];
int sc[N];
int di[N];
bool vis[N];

vector<int>g[N];

void add(int a,int b,int c){
	e[idx] = b  , f[idx] = c, ne[idx] = h[a] , h[a] = idx++;
	e[idx] = a  , f[idx] = 0, ne[idx] = h[b] , h[b] = idx++;
}

void topusort(){//拓扑找环
	queue<int>q;
	for(int i=1;i<=n*m;i++){
		if(di[i] == 0)q.ps(i);
	}
	
	while(!q.empty()){
		int t  = q.front();
		q.pop();
		vis[t] = true;
		for(auto it:g[t]){
			int ver = it;
			di[ver]--;
			if(di[ver]==0)q.ps(ver);//权值为0说明即不是环上的点也不是被环保护的点
		}
	}
}

bool bfs(){
	int hh = 0 , tt = 0;
	mm(d,-1,sizeof d);
	cur[S] = h[S] , d[S] = 0 , q[0] = S;
	
	while(hh<=tt){
		int t = q[hh++] ;
		for(int i=h[t] ; ~i ; i= ne[i]){
			int ver = e[i];
			if(d[ver] == -1 && f[i]){
				d[ver] = d[t] +1;
				cur[ver] = h[ver];
				if(ver == T)return true;
				q[++tt] = ver;
			}
		}
	}
	return false;
}

int find(int u,int limit){
	if(u==T)return limit;
	int flow = 0;
	for(int i=cur[u];~i && flow < limit ; i = ne[i]){
		cur[u] = i;
		int ver = e[i];
		if(d[ver] == d[u]+1 && f[i]){
			int t = find(ver , min(f[i],limit - flow));
			if(!t)d[ver] = -1;
			f[i] -= t , f[i^1] += t , flow += t;
		}
	}
	return flow;
}

int dinic(){
	int r = 0 , flow;
	while(bfs())while(flow = find(S,INF)) r += flow;
	return r;
}

int get(int i,int j){
	return (i-1)*m+j;
}

vector<PII>ptt[2000];

void solve(){
	mm(h,-1,sizeof h);
	cin >> n >> m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			
			int u = get(i,j),v = u+1;
			cin >> sc[u];
			
			if(j<m){//右边的植物向左边的植物连边
				di[u]++;//左边的植物的入度+1
				g[v].pb(u);
			}
			
			int q;cin >> q;
			while(q--){
				int x,y;
				cin >> x >> y;
				x++,y++;//数据编号从0开始,需要+1
				int p = get(x,y);//计算下标
				di[p]++;//被保护的植物入度+1
				g[u].pb(p);//从保护植物的植物连向被保护的植物
				ptt[u].pb({x,y});
			}
		}
		
	}
	S = 0 , T = n*m+1;
	topusort();
	
	int sum = 0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			int u = get(i,j), v = u+1;
			if(!vis[u])continue;
			if(j<m && vis[v])add(u,v,INF);//边转置流量为无穷
			if(sc[u]>=0){
				add(S,u,sc[u]);//从源点连向正权点流量为该点的权值
				sum += sc[u];//记录正权值之和
			}else{
				add(u,T,-1*sc[u]);//从负权点向汇点连一条流量为该权值的绝对值的边
			}
			
			for(int k = 0 ;k<ptt[u].size();k++){
				int x = ptt[u][k].first , y = ptt[u][k].second;
				int p = (x-1)*m + y;
				add(p,u,INF);//边转置流量为无穷
			}
		}
	}
	// cout << "sum==" << sum << " dinic==  " << dinic() << "\n";
	cout << sum - dinic();//正权值之和-dinic
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int t;
	// cin >> T;
	t = 1;
	while(t--){
		solve();
	}
	
}

03/04/2024 15:39

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐流海

代码点亮未来

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值