BZOJ 1565 植物大战僵尸 最大权闭合子图+网络流

题意:

  植物大战僵尸,一个n*m的格子,每 个格子里有一个植物,每个植物有两个属性:

  (1)价值;

  (2)保护集合,也就是这个植物可以保护矩阵中的某些格子。

  现在你是僵尸,你每次只能从(i,m) 格子进入,从右向左进攻。若一个格子是被保护的那么你是不能进入的。每进入一个格子则吃掉该格子的植物并得到其价值(价值有可能是负的),可以中途返回。问可以得到的最大价值是多少?

分析:

  这是一道比较真实的题目。(真打游戏的时候应该也是这种景象吧)

  首先我们复习一下最大权闭合子图的特质。

  有一个有向图,每一个点都有一个权值(可以为正或负或0),选择一个权值和最大的子图,使得每个点的后继都在子图里面,这个子图就叫最大权闭合子图。

  上面这句话我们要提炼出一个极其重要的信息:如果我们选择一个点,那就必须选择它的所有后继。

  凡是题目中隐含着这样的条件的,我们都可以往最大权闭合子图方向去想一想。

  像这道题,每个植物可以保护一些其他的植物。那就意味着,如果我们想要选择一个植物,我们必须首先把所有保护它的植物都选掉。

  这样,我们就可以建图,对于一个点x,假如有点y可以保护点x,那么我们就连一条x—>y的边,注意,边的方向和保护的方向是相反的。

  (这里有好多题解都不是这么说的,或许另有高论?)

  还隐含着一个条件:右边的始终植物保护着左边的植物(对吧?植物大战僵尸里是这样的吧,所以诞生了高坚果)

  我们建图,跑最大权闭合子图就好了吗?

  并不是……

  因为环是无敌的……???!!!

  如果在保护关系中出现了环,那么你选任何一个,都是被保护的。

  所以我们拓扑,把环的影响取消掉,再跑最大权闭合子图的恶意……

  从源点s向每个正权点连一条容量为权值的边,每个负权点向汇点t连一条容量为权值的绝对值的边,有向图原来的边容量全部为无限大答案为正权值之和-最小割

代码:

 

 1 #include<bits/stdc++.h>
 2 #define ms(a,x) memset(a,x,sizeof(a))
 3 using namespace std;int tot=0,n,m,sm=0;
 4 const int N=2005,inf=0x3f3f3f3f;
 5 struct node{int y,z,nxt;}e[N*400];
 6 int h[N],c=1,q[N],in[N],S,T,d[N],a[N],ans=0;
 7 void add(int x,int y,int z){in[x]++;
 8     e[++c]=(node){y,z,h[x]};h[x]=c;
 9     e[++c]=(node){x,0,h[y]};h[y]=c;
10 } bool bfs(){
11     for(int i=S;i<=T;i++) 
12     if(d[i]!=-2) d[i]=-1;
13     int f=1,t=0;d[S]=1;q[++t]=S;
14     while(f<=t){
15         int x=q[f++];
16         for(int i=h[x],y;i;i=e[i].nxt)
17         if(d[y=e[i].y]==-1&&e[i].z)
18         d[y]=d[x]+1,q[++t]=y;
19     } return (d[T]>0);
20 } int dfs(int x,int f){
21     if(x==T) return f;int w,tmp=0;
22     for(int i=h[x],y;i;i=e[i].nxt)
23     if(d[y=e[i].y]==d[x]+1&&e[i].z){
24         w=dfs(y,min(e[i].z,f-tmp));
25         if(!w) d[y]=-1;e[i].z-=w;
26         e[i^1].z+=w;tmp+=w;
27         if(tmp==f) return f;
28     } return tmp;
29 } void solve(){
30     while(bfs()) tot+=dfs(S,inf);
31 } int main(){
32     scanf("%d%d",&n,&m);S=0;T=n*m+1;
33     for(int i=1,tmp;i<=n*m;i++){
34         scanf("%d",&a[i]);
35         a[i]>0?add(S,i,a[i]):add(i,T,-a[i]);
36         scanf("%d",&tmp);while(tmp--){
37             int x,y;scanf("%d%d",&x,&y);
38             add(x*m+y+1,i,inf);
39         } if(i%m) add(i,i+1,inf);
40     } int f=1,t=0;//图是反着建的,拓扑要倒过来 
41     for(int i=S;i<=T;d[i]=-2,i++)
42     if(!in[i]) q[++t]=i; 
43     while(f<=t){
44         int x=q[f++];d[x]=0;
45         if(a[x]>0) sm+=a[x];
46         for(int i=h[x];i;i=e[i].nxt)
47         if(i&1)if(!--in[e[i].y]) q[++t]=e[i].y;
48     } solve();
49     printf("%d\n",sm-tot);return 0; 
50 }
最大权闭合子图

 

转载于:https://www.cnblogs.com/Alan-Luo/p/10255461.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值