最小割求最大权闭合子图整理

  • 参考资料:

  • CaptainHarryChen

  • 洛谷题解

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

  • 求解问题:选一部分这点,选了这部分点必须要选另一些点,求怎么选从权值最大。

  • 建图方法:源点s向所有正权点连容量为点权的边,所有点权为负的边向汇点t连容量为点权绝对值的边,按照原图连容量为inf的边。
    在这里插入图片描述

  • 答案:最大权闭合子图的权值为正权值和-最小割

  • 个人理解:最大权闭合子图的权值和 = max{被选择的点权和} = 正点权和−min{没被选择的正权点之和 + 被选择的负权点绝对值和} = 正点权和−最小割;即考虑min{没被选择的正权点之和 + 被选择的负权点绝对值和} = 正点权和

  • 从割的角度看

由于原图的边都是无穷大,那么割边一定是与源点s或汇点t相连的。

割掉s与i的边,表示不选择i点作为子图的点;(当成前提条件理解,可推下面)
割掉i与t的边,表示选择i点为子图的点。(因为是最小割,割掉i与t的边意味着一定存在至少一个i的前驱点于s的边未被割)

如果s与i有边,表示i存在子图中;
如果i与t有边,表示i不存在于子图中。

  • 根据割的意义得到合法性

1.因为割s->i意味着不选,割j->t意味着选,割不同属性的边含义不同,所以s与t联通意味着存在没选已选点的后继点的情况,所以要求割破坏图的连通性。
2.如果s与t不连通,选择了正权点i,一定选择了i后继中的所有负权点。设j是i的后继中的正权点,则割掉s到j的边是没有意义的,最小割不会割掉它,则j一点被选中,所以i的所有后继都被选中,符合闭合图的定义。
3(最优性).根据在本问题中割的含义(没选的正权点权值和和与选了的负权点的绝对值权值和),所有正权点的权值和减去这个值即为一个方案,若减的是最小割即为最大权闭合子图的点权和。

  • 方案统计:对于正权点,割完之后依旧联通的点为方案;对于负权点,割掉的边所连的点为选择的点,在网络流图中即为t->j联通的点。具体实现通过dinic里用到的记录分层情况的数组来判断,看看是否有分层,因为如果正权点有分层,那么他们的后继点一定有分层(之间边的容量inf)
  • 太空飞行计划问题
#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int inf=0x7f7f7f7f;
int n,m,cnt=1,last[2505],cur[2505],ceng[2505],ans,st=150,flow;;
struct edge{
    int v,f,w,next;
}e[40005];
inline void add(int u,int v,int w)
{
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].next=last[u];
    last[u]=cnt;
}
bool bfs()
{
    queue<int>q;
    for(int i=1;i<=st;i++)
    ceng[i]=0;
    ceng[0]=1;
    q.push(0);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=last[u];i;i=e[i].next)
        {
            int v=e[i].v,w=e[i].w;
            if(!ceng[v]&&w)
            {
                ceng[v]=ceng[u]+1;
                q.push(v);
            }
        }
    }
    return ceng[st];
}
int dfs(int u,int dis)
{
    if(u==st||!dis)
        return dis;
    for(int i=cur[u];i;i=e[i].next)
    {
        cur[u]=e[i].next;
        int v=e[i].v,w=e[i].w;
        if(ceng[v]==ceng[u]+1&&w)
        {
            int di=dfs(v,min(dis,w));
            if(di>0)
            {
                e[i].w-=di;
                e[i^1].w+=di;
                return di;
            }
        }
    }
    return 0;
}
void dinic()
{
    while(bfs())
    {
        for(int i=0;i<=st;i++)
        cur[i]=last[i];
        while(int d=dfs(0,inf))
        flow+=d;
    }
}
void getn(int u)
{
    int w;
    scanf("%d",&w);
    char tools[10000];
    memset(tools,0,sizeof tools);
    cin.getline(tools,10000);
    int ulen=0,tool;

    ans+=w;
    add(0,u,w);
    add(u,0,0);
    while (sscanf(tools+ulen,"%d",&tool)==1)
    {
        add(u,tool+m,inf);
        add(tool+m,u,0);
        if (tool==0)
        ulen++;
        else {
        while (tool) {
            tool/=10;
            ulen++;
        }
        }
        ulen++;
    }
}
int main()
{
    cin>>m>>n;
    for(int i=1;i<=m;i++)
    getn(i);
    for(int i=1,w;i<=n;i++)
    {
        scanf("%d",&w);
        add(i+m,st,w);
        add(st,i+m,0);
    }
    dinic();
    for(int i=last[0];i;i=e[i].next)
    if(ceng[e[i].v])
    cout<<e[i].v<<' ';
    cout<<endl;
    for(int i=last[st];i;i=e[i].next)
    if(ceng[e[i].v])
    cout<<e[i].v-m<<' ';
    cout<<endl;
    cout<<ans-flow;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哈希表扁豆

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

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

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

打赏作者

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

抵扣说明:

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

余额充值