闭合图:对于一个有向图G,存在点集合V,任取点u属于V,u的出边的另一个点也属于V,则为闭合图。
理解:任取一起点,终点必定是无出度的点。
最大权闭合子图:当每个点有一个权值w(有正有负),点权和最大的闭合图为最大权闭合子图。
如图:
最大权闭合子图为点集{3,4,5},最大权为7+0-3=4。
求解方法:网络流。
建立超级源点s,超级汇点t。
所有点权为正数的点i,建边 s->i,容量为点权。
所有点权为负数的点i,建边i->t,容量为点权绝对值。
原图建图后,边容量均为正无穷。
最大权闭合图的权值 = 正权点之和 - (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
【例题】RMRC2017
5120: Open-Pit Mining
时间限制: 1 Sec 内存限制: 128 MB提交: 19 解决: 10
[ 提交][ 状态][ 讨论版][命题人: admin]
题目描述
its profits by open-pit mining. ACM has hired you to write a program that will determine the maximum profit it can achieve given the description of a piece of land.
Each piece of land is modelled as a set of blocks of material. Block i has an associated value (vi), as well as a cost (ci), to dig that block from the land. Some blocks obstruct or bury other blocks. So for example if block i is obstructed by blocks j and k, then one must first dig up blocks j and k before block i can be dug up. A block can be dug up when it has no other blocks obstructing it.
输入
Then follow N lines describing these blocks. The ith such line describes block i and starts with two integers vi, ci denoting the value and cost of the ith block (0≤vi,ci≤200).
Then a third integer 0≤mi≤N-1 on this line describes the number of blocks that block i obstructs.
Following that are mi distinct space separated integers between 1 and N (but excluding i) denoting the label(s) of the blocks that block i obstructs.
You may assume that it is possible to dig up every block for some digging order. The sum of values mi over all blocks i will be at most 500.
输出
样例输入
5
0 3 2 2 3
1 3 2 4 5
4 8 1 4
5 3 0
9 2 0
样例输出
2
【题意】
某公司要开采一些矿井,但是一些矿井会被别的井挡住,必须先开了挡住的井。每个井有个开采价值vi,有个开采成本ci
现在可以开采一些矿井,问最大能获得的利润(大概这么个事吧,四级没过....)
【分析】
重点是能将此题有最大权闭合子图联系起来。要开采某矿井,必须开采它前面的矿井,符合闭合图。
每个点可以求出利润wi = vi - ci,作为点权。
若i被j挡住,那么就建立边i->j,然后按照最大权闭合子图建图,最大流模板即过。
【代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
const int INF=0x3f3f3f3f;
struct node{
ll t,cap,flow,next; //cap容量,flow流量
}e[N];
int head[N],cur[N],cnt; //cur优化dfs中的head
void init(){
memset(head,-1,sizeof(head));
cnt=0;
}
void add(int u,int v,ll cap) //u->v容量为cap
{
e[cnt]=node{v,cap,0,head[u]};
head[u]=cnt++;
e[cnt]=node{u,0,0,head[v]}; //容量为0的反向边
head[v]=cnt++;
}
int d[N]; //bfs深度
bool bfs(int s,int t) //O(n+m)
{
memset(d,0,sizeof(d));
queue<int>q;
q.push(s);
d[s]=1;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];~i;i=e[i].next)
{
int v=e[i].t;
if(d[v]==0&&e[i].cap-e[i].flow>0)
{
d[v]=d[u]+1;
q.push(v);
}
}
}
return d[t]>0; //存在增广路
}
ll dfs(int s,int t,ll minedge)
{
if(s==t)return minedge;
ll flow=0; //从当前s点流出的流量
for(int &i=cur[s];~i;i=e[i].next)
{
int v=e[i].t;
if(d[v]==d[s]+1&&e[i].cap-e[i].flow>0) //层次关系&&有剩余流量
{
ll temp=dfs(v,t,min(minedge-flow,e[i].cap-e[i].flow));
e[i].flow+=temp; //流量增加
e[i^1].flow-=temp; //反向边流量减少
flow+=temp; //flow已分配的流量
if(flow==minedge)return flow; //已达到祖先的最大流,无法再大,剪枝
}
}
if(flow==0)d[s]=0; //此点已无流,标记掉
return flow;
}
ll dinic(int s,int t) //一定要建立反向边cap=0
{
ll maxflow=0;
while(bfs(s,t)) //有增广路
{
memcpy(cur,head,sizeof(head)); //重要的优化
maxflow+=dfs(s,t,INF);
}
return maxflow;
}
int pro[220];
int main()
{
int n,u,v,x,k;
init();
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&u,&v,&k);
pro[i]=u-v;
while(k--){
scanf("%d",&x);
add(x,i,INF);
}
}
ll sum=0;
for(int i=1;i<=n;i++)
{
if(pro[i]>0){
add(0,i,pro[i]);
sum+=pro[i];
}
else if(pro[i]<0)add(i,n+1,-pro[i]);
}
int ans=dinic(0,n+1);
cout<<sum-ans<<endl;
return 0;
}