最大权闭合子图(RMRC2017 Open-Pit Mining)

闭合图:对于一个有向图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]

题目描述

Open-pit mining is a surface mining technique of extracting rock or minerals from the earth by their removal  from an open pit or borrow. Open-pit mines are used when deposits of commercially useful minerals or rocks  are found near the surface. Automatic Computer Mining (ACM) is a company that would like to maximize
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.

输入

The first line of input is an integer N (1≤N≤200) which is the number of blocks. These blocks are numbered 1 through N.
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.

输出

Output a single integer giving the maximum profit that ACM can achieve from the given piece of land.

样例输入

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;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪的期许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值