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

380

被折叠的 条评论
为什么被折叠?



