【洛谷2805】[NOI2009] 植物大战僵尸(最大权闭合子图)
- 一张\(n\times m\)的网格图,每个位置上有一个植物。
- 每个植物有一个价值(可能为负)和一个攻击位置集合,吃一个植物要先吃掉它右边的所有植物以及所有能攻击到这个位置的植物。
- 求能获得的最大价值。
- \(n\le20,m\le30\)
最大权闭合子图
经典问题,完全就是个板子。
如果吃植物\(x\)需要先吃植物\(y\),就从\(x\)向\(y\)连一条容量为\(INF\)的边。
然后令答案的初值为所有正权值之和,从超级源向所有正权点\(x\)连一条容量为\(a_x\)的边表示不选的代价,从所有负权点向超级汇连一条容量为\(-a_x\)的边表示选的代价。
最后跑一遍最小割=最大流即可。
拓扑去环
但实际上还有一个坑点,这道题中可能连出环来!(实际上样例中就有这种情况)
因此我们要先跑一遍拓扑,则拓扑跑不到的点就是无论如何都无法吃到的点,直接删去。
注意,由于我们是从被限制的点向限制的点连边的,要在反图上拓扑。(直接拓扑能惊喜地收获80分)
代码:\(O(Dinic)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 20
#define M 30
#define INF (int)1e9
#define P(x,y) (((x)-1)*m+(y))
using namespace std;
int n,m,a[N*M+5];
namespace D
{
#define s (n*m+1)
#define t (n*m+2)
#define adde(x,y,f) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f)
const int PS=N*M+2,ES=N*M+N*M*N*M;
int ee=1,lnk[PS+5],cur[PS+5];struct edge {int to,nxt,F;}e[2*ES+5];
I void Add(CI x,CI y,CI f) {adde(x,y,f),adde(y,x,0);}
int q[PS+5],d[PS+5];I bool BFS() {RI i,k,H,T;for(i=1;i<=t;++i) d[i]=0;d[q[H=T=1]=s]=1;
W(H<=T&&!d[t]) for(i=lnk[k=q[H++]];i;i=e[i].nxt) !d[e[i].to]&&e[i].F&&(d[q[++T]=e[i].to]=d[k]+1);return d[t];}
I int DFS(CI x=s,RI f=1e9) {if(x==t||!f) return f;RI o,g=0;for(RI &i=cur[x];i;i=e[i].nxt)
if((d[x]+1)==d[e[i].to]&&(o=DFS(e[i].to,min(f,e[i].F)),e[i].F-=o,e[i^1].F+=o,g+=o,!(f-=o))) break;return g;}
I int MaxFlow() {RI g=0;W(BFS()) memcpy(cur,lnk,sizeof(lnk)),g+=DFS();return g;}
int p[PS+5];I void Topo()//反图拓扑
{
RI i,H=1,T=0;for(i=2;i<=ee;++i) !e[i].F&&++d[e[i].to];for(i=1;i<=n*m;++i) !d[i]&&(q[++T]=i);
RI k;W(H<=T) for(p[k=q[H++]]=1,i=lnk[k];i;i=e[i].nxt) !e[i].F&&!--d[e[i].to]&&(q[++T]=e[i].to);
}
}
int main()
{
RI i,j,x,y,z;for(scanf("%d%d",&n,&m),i=1;i<=n*m;++i)
for(scanf("%d%d",a+i,&z);z;--z) scanf("%d%d",&x,&y),++x,++y,D::Add(P(x,y),i,INF);//攻击限制
for(i=1;i<=n;++i) for(j=1;j^m;++j) D::Add(P(i,j),P(i,j+1),INF);//位置限制
RI g=0;for(D::Topo(),i=1;i<=n*m;++i) D::p[i]&&(a[i]>0&&(D::Add(s,i,a[i]),g+=a[i]),a[i]<0&&(D::Add(i,t,-a[i]),0));//未删去的点与超级源汇连边
return printf("%d\n",g-D::MaxFlow()),0;//总价值-最小割
}
待到再迷茫时回头望,所有脚印会发出光芒