【相关概念详解】
闭合图:有向图的一个点集,且这个点集的所有出边仍然指向该点集。
最大权闭合图:(每一个点有一个权值)在所有的合法闭合图中,点权之和最大的图。
处理问题:权值有正有负,重复选只算一次,选择有相互关联性 的问题。
首先有一个有向连通图(闭合图),每个点带有一个权值,例如:
造出一个超级源点S和一个超级汇点T,把S连边到所有带有正权的点上,每条边的容量是这个点的权;
把所有带负权的点连边到T,每条边的容量是这个点的权的相反数(正值)。原来的边的容量设成无限大。
所有的点按权值的正负连接到s和t上,转换成一个边权值有向图。
最大权闭合图的最大权 = 所有正权值之和 — 最小割容量(最大流的值) 。
步骤归纳:1. 建立超级源点s,超级汇点t。
2. 所有点权为正数的点i,建边 s->i,容量为(正)点权。
3. 所有点权为负数的点i,建边i->t,容量为(负)点权绝对值。
4. 原图建图后,(互相关联影响的)边容量设为正无穷。
5.最大权闭合图的权值 = 正权点之和 - (s->t)最大流。
建图方法的推导
源点s可以理解为最大可获得的权值(即正权点之和)。
汇点t可以理解为最大的会损失的权值(负权点之和)。
我们现在要尽量的获得s,而避免t。
想选出最大权闭合图,则如果选定一个点,这个点的所有后继子孙点都必须选择。
因此,想拿正数,就不可避免的要取负数子孙后继点。
所以:要尽量选择正权点为起点,才有可能增大闭合图点集的权值和。
因此,我们从源点s向正权点连边(即只以正权点为起点)。
至于过程中遇到的负权点,我们让其流向t,即建立边 负权点->t的意图。
好,现在我们尽量去取正数点(直接从源点s起始),过程中遇到的负权点流向t。
现在就变成了:s->t的流量就是我们的损失。
即:我们希望流向t的流量flow尽量少,而从s流出的流量sum尽量多,
从s流出而没有流入t的流量(sum-flow)就是闭合图的最大权。
可能有种情况很懵逼:
若要选点,选2吧,权为-5;选1和2吧,权为-1;不如选空集,权为0。明显最后的选择是对的。
但按照上面的思路,从s流出4,所以损失最多为4,sum-flow=0。
所以对该图就产生这么一种结论:
选择1号点,和不选1号点,结果是相同的,我选的时候他会被损失完,效果等同于不选。
那不妨我一开始就把所有的正权点都给选了(满足从s流出的最多),让他们往后代流,
大不了被负权子孙点损失完,而那些没有被损失完的,就是我们统计下来的结果。
那个损失,就是s->t的最大流。于是得证:闭合图最大权 = 正权和sum - 最大流flow。
【例题1】【p4174】最大获利
#include #include #include #include #include #include #include
【p4174】最大获利 // 网络流 + 最大权闭合子图【模板题】
【例题2】【p3749】寿司餐厅
#include #include #include #include #include #include #include
【p3749】寿司餐厅 // 网络流 + 最大权闭合子图 + 读题/细节处理
【例题3】【p2762】太空飞行问题
#include #include #include #include #include #include #include
【p2762】太空飞行问题 // 网络流 + 最大权闭合子图 + 读入方式 + 输出方案
【例题4】【poj2987】Fireing
#include #include #include #include #include #include #include
【poj2987】Firing // 网络流 + 最大权闭合子图 + 不开ll见祖宗 #include #include #include #include #include #include #include
【poj2987】Firing // 网络流 + 最大权闭合子图 + 第二种找路径的方法 time--
【例题5】【p2805】植物大战僵尸
#include #include #include #include #include #include #include
q; for(int i=1;i<=n*m;i++) //统计入度 for(int j=1;j<=n*m;j++) if(g[i][j]) rd[j]++; for(int i=1;i<=n*m;i++) if(!rd[i]) q.push(i); while(!q.empty()){ int x=q.front(); q.pop(),vis_[x]=1; for(int y=1;y<=n*m;y++) if(g[x][y]) { rd[y]--; if(!rd[y]) q.push(y); } }}void dfs(int x){ for(int i=1;i<=n*m;i++) if(g[x][i]&&vis_[i]) vis_[i]=0,dfs(i); }int main(){ reads(n),reads(m); s=0,t=n*m+1; memset(head,-1,sizeof(head)); for(int i=1,k,x,y;i<=n;i++) for(int j=1;j<=m;j++){ reads(w[(i-1)*m+j]),reads(k); if(j>1) g[(i-1)*m+j][(i-1)*m+j-1]=1; while(k--) reads(x),reads(y),g[(i-1)*m+j][x*m+y+1]=1; } topo(); for(int i=1;i<=n*m;i++) if(!vis_[i]) dfs(i); //拓扑排序判环,并把环上所有元素重新标记为vis_[i]==0 //方法:环上的各点入度都为1,不为0,拓扑排序后肯定还有点没进队列 for(int i=1;i<=n*m;i++) if(vis_[i]){ if(w[i]>=0) add(s,i,w[i]),sum+=w[i]; else add(i,t,-w[i]); for(int j=1;j<=n*m;j++) if(g[j][i]&&vis_[j]) add(i,j,inf); } cout<
< 【p2805】植物大战僵尸 // 网络流 + 最大权闭合子图 + 拓扑排序