最大权闭合子图

有向图闭合图:

闭合图内任意点的任意后继都在闭合图中。
要创造一个闭合子图,如果选择了一个点,就必须把这个点的所有后继都选上。

可以用于表示事物间依赖关系:
如果一个事件要发生,它需要的所有前提(图中为后继)也必须都要发生。

最大权闭合子图:

点权之和最大的闭合子图

建图:

源点->原图正点权事件,容量为点权
原图负权点事件->汇点,容量为点权绝对值
事件边的边权修改为inf即(前驱->后继,容量inf)

(据说点权为0的可以忽略,不影响结果)

闭合图的权为正权点总和减去该图的割
当割最小时,闭合图权最大

如果源点与点x有边,表示点x存在子图中;
如果点x与汇点有边,表示点x不存在于子图中。


P4174 最大获利

在这里插入图片描述

思路:

建立中转站看作一个点
为用户提供服务也看作一个点
显然建立中转站点权为负(因为要成本)
为用户提供服务点权为正(因为获益)

然后考虑两者之间的关系:
选择了某个用户,如果该用户要从a->b收益c
则说明选择了这个用户必须选择建立中转站a和中转站b
显然建立中转站a、b为其后继

建图:
事件点->中转站a , 事件点->中转站b ,容量为inf
源点->事件点 ,容量为获益c
中转站->汇点,容量为成本p

求最小割,最后的答案为正权之和-最小割

code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxm=2e5+5;
const int inf=1e9;
int head[maxm],nt[maxm<<1],to[maxm<<1],w[maxm<<1],cnt;
int d[maxm];
int n,m;
int s,t;
int maxflow;
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
    cnt++;nt[cnt]=head[y];head[y]=cnt;to[cnt]=x;w[cnt]=0;
}
bool bfs(){
    queue<int>q;
    q.push(s);
    memset(d,0,sizeof d);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(w[i]&&!d[v]){
                d[v]=d[x]+1;
                q.push(v);
                if(v==t)return 1;
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            w[i]-=k;
            w[i^1]+=k;
            res-=k;
            if(!k)d[v]=-1;
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
int main(){
    init();
    scanf("%d%d",&n,&m);
    s=0,t=n+m+1;
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        add(i,t,x);
    }
    int sum=0;
    for(int i=1;i<=m;i++){//事件变为点1+n~m+n
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        sum+=c;
        add(s,i+n,c);
        add(i+n,a,inf);
        add(i+n,b,inf);
    }
    dinic();
    printf("%d\n",sum-maxflow);
    return 0;
}

poj2987 Firing

题意:

公司要解雇一些人,某些人解雇可以获益,某些人解雇会亏钱,解雇一个人则这个人手下的所有人都必须解雇,也就是说一个人的上司被解雇了这个人也会被解雇,现在给n个人解雇的收益或损失以及这n个人的从属关系,求得到最大收益需要解雇的人数和最大收益。

思路:

显然要求得是闭合子图的最大权,收益很简单直接求就行了,但是还要求输出解雇的人数,
根据和源点相连的点都是要选择的,所以可以在求完最小割之后的残余网络上从源点开始dfs,统计搜到的点就行了,注意要mark搜到的点防止再搜,否则可能死循环

ps:
题目数据比较大需要longlong

code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
#define int long long
const int maxm=2e5+5;
const int inf=1e18;
int head[maxm],nt[maxm<<1],to[maxm<<1],w[maxm<<1],cnt;
int mark[maxm];
int d[maxm];
int n,m;
int s,t;
int maxflow;
int ans;
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
    cnt++;nt[cnt]=head[y];head[y]=cnt;to[cnt]=x;w[cnt]=0;
}
bool bfs(){
    queue<int>q;
    q.push(s);
    memset(d,0,sizeof d);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(w[i]&&!d[v]){
                d[v]=d[x]+1;
                q.push(v);
                if(v==t)return 1;
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            w[i]-=k;
            w[i^1]+=k;
            res-=k;
            if(!k)d[v]=-1;
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
void getans(int x){
    mark[x]=1;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&!mark[v]){
            ans++;
            getans(v);
        }
    }
}
signed main(){
    init();
    scanf("%lld%lld",&n,&m);
    s=0,t=n+1;
    int sum=0;
    for(int i=1;i<=n;i++){
        int x;
        scanf("%lld",&x);
        if(x>0){
            sum+=x;
            add(s,i,x);
        }else{
            add(i,t,-x);
        }
    }
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%lld%lld",&a,&b);
        add(a,b,inf);
    }
    dinic();
    getans(s);
    printf("%lld %lld\n",ans,sum-maxflow);
    return 0;
}

CodeForces 1082 G.Petya and Graph

题意:

n个点m条边的图,点有点权,边有边权
要求选一出一个子图,使得边权和减去点权和最大

如果选择了某一条边,则边的两个端点都必须选

思路:

最大权闭合子图裸题

如果选择了某一条边,则边的两个端点都必须选,可以看出事件的先后关系。

显然边是正权事件,点是负权事件
建图:
源点->边,容量为边权
边->端点1,边->端点2,容量为inf
点->汇点,容量为点权

答案为边权和减去最小割(即最大流)

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf=1e17;
const int maxm=3e3+5;
int head[maxm],nt[maxm<<2],to[maxm<<2],w[maxm<<2],cnt;
int d[maxm];
int p[maxm];
int n,m;
int s,t;
int maxflow;
void init(){
    memset(head,-1,sizeof head);
    cnt=1;
}
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
    cnt++;nt[cnt]=head[y];head[y]=cnt;to[cnt]=x;w[cnt]=0;
}
bool bfs(){//分层图
    queue<int>q;
    q.push(s);
    memset(d,0,sizeof d);
    d[s]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i!=-1;i=nt[i]){
            int v=to[i];
            if(w[i]&&!d[v]){
                d[v]=d[x]+1;
                q.push(v);
                if(v==t)return 1;
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){//求流
    if(x==t)return flow;
    int res=flow;
    for(int i=head[x];i!=-1;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            w[i]-=k;
            w[i^1]+=k;
            res-=k;
            if(!k)d[v]=-1;
            if(!res)break;
        }
    }
    return flow-res;
}
void dinic(){
    maxflow=0;
    while(bfs()){
        maxflow+=dfs(s,inf);
    }
}
signed main(){
    init();
    cin>>n>>m;
    s=0,t=n+m+1;
    for(int i=1;i<=n;i++){
        cin>>p[i];
        add(i,t,p[i]);//负权事件连接汇点
    }
    int sum=0;
    for(int i=1;i<=m;i++){//边(n+1)-(n+m)
        int a,b,c;
        cin>>a>>b>>c;
        sum+=c;//累加至正权和
        add(s,n+i,c);//源点到边,源点连正权事件
        add(n+i,a,inf);//事件边连接两个端点
        add(n+i,b,inf);//事件边连接两个端点
    }
    dinic();
    cout<<sum-maxflow<<endl;
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值