【板子】Dinic算法

6 篇文章 0 订阅
4 篇文章 0 订阅

关于Dinic算法

  • 引言
    Dinic是求解网络最大流的经典算法之一,它由最简单但效率不够的寻找增广路算法优化而来。
  • 实现
    1.建图
    2.BFS构建层次图,判断是否存在由源点到汇点的一条当前最短路径
bool Bfs() {
    memset(lev,0,sizeof(lev));//用于记层次的数组lev[]
    int t; queue<int>q;
    q.push(S),lev[S]=1;
    while(!q.empty()) {
        t=q.front(),q.pop();
        for(int i=hd[t];i;i=nxt[i])
            if(!lev[to[i]]&&c[i]>0) { //每次Dfs进行路径増广时更新流量c,需要判断
                lev[to[i]]=lev[t]+1;
                if(to[i]==T) return 1;
                q.push(to[i]);
            }
    }
    return lev[T];
}

        3.若存在,DFS对源点到汇点上的路径进行増广,即找到増广路上容量最小的路径容量 Fmin ,再将增广路上的路径流量进行更新。如此反复直到无法寻找到増广路为止。

int Dfs(int x,int mx) {
    if(x==T||!mx) return mx; //到达终点或无法继续増广
    int f;
    for(int i=cur[x]?cur[x]:hd[x];i;i=nxt[i]) { //cur[i]记遍历点i时的末边
        cur[x]=i; 
        if(lev[to[i]]==lev[x]+1&&c[i]>0) { //层次相邻且可以増广
            if(f=Dfs(to[i],Min(mx,c[i]))) //取Fmin
                return c[i]-=f,c[i^1]+=f,f; //边i对应反向边i^1 边数tot需初始化为奇数
        }
    }
    return 0;
}

        循环反复

int Dinic() {
    int f,Flow=0;
    while(Bfs()) {
        memset(cur,0,sizeof(cur));
        while(f=Dfs(S,Inf)) Flow+=f;
    }
    return Flow;
}

        4.否则得到最大流,结束算法

例题

6001. 「网络流 24 题」太空飞行计划

题目描述

    W 教授正在为国家航天中心计划一系列的太空飞行。每次太空飞行可进行一系列商业性实验而获取利润。现已确定了一个可供选择的实验集合 E={E1,E2,⋯,Em} E = { E_1, E_2, \cdots, E_m }E={E​1​​,E​2​​,⋯,E​m​​},和进行这些实验需要使用的全部仪器的集合 I={I1,I2,⋯,In} I = { I_1, I_2, \cdots, I_n }I={I​1​​,I​2​​,⋯,I​n​​}。实验 Ej E_jE​j​​ 需要用到的仪器是 I II 的子集 Rj⊆I R_j \subseteq IR​j​​⊆I。

    配置仪器 Ik I_kI​k​​ 的费用为 ck c_kc​k​​ 美元。实验 Ej E_jE​j​​ 的赞助商已同意为该实验结果支付 pj p_jp​j​​ 美元。W 教授的任务是找出一个有效算法,确定在一次太空飞行中要进行哪些实验并因此而配置哪些仪器才能使太空飞行的净收益最大。这里净收益是指进行实验所获得的全部收入与配置仪器的全部费用的差额。

    对于给定的实验和仪器配置情况,编程找出净收益最大的试验计划。

输入格式

    第 1行有 2 个正整数 m 和 n 。m是实验数,n是仪器数。接下来的 m 行,每行是一个实验的有关数据。第一个数赞助商同意支付该实验的费用;接着是该实验需要用到的若干仪器的编号。最后一行的 n个数是配置每个仪器的费用。

输出格式

第 行是实验编号,第 2 行是仪器编号,最后一行是净收益。

样例

样例输入

10 1 2
25 2 3
5 6 7

样例输出

1 2 3
17

数据范围与提示

1≤n,m≤50

分析

该题正解为将 求解最大权闭合图 (转化)–> 求解最小割 (转化)–> 求解最大流
具体转化过程戳这里 hiho 第119周 最大权闭合子图

最终最大流的求解使用Dinic算法

代码

#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;

const int sm = 105,sn = 2505;
const int Inf = 0x3f3f3f3f;

int N,M,tot=1,sum,S,T,Flow;
int to[sn],hd[sm],nxt[sn],c[sn];
int lev[sm],cur[sm];
int a[sm],b[sm][sm],ct[sm];
bool ex[sm];

int Min(int x,int y) { return x<y?x:y; }
char ch;
void read(int &x) {
    x=0;ch=getchar();
    while(ch>'9'||ch<'0') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
}
void Add(int u,int v,int w) {
    to[++tot]=v,nxt[tot]=hd[u],hd[u]=tot,c[tot]=w;
}
bool Bfs(int S,int T) {
    memset(lev,0,sizeof(lev));
    queue<int>q;
    q.push(S),lev[S]=1;
    while(!q.empty()) {
        int t=q.front();q.pop();
        for(int i=hd[t];i;i=nxt[i]) 
            if(!lev[to[i]]&&c[i]) {
                lev[to[i]]=lev[t]+1;
                if(to[i]==T) return 1;
                q.push(to[i]);
            }
    }
    return lev[T];
}
int Dfs(int x,int mx) {
    if(!mx||x==T) return mx;
    int f=0;
    for(int i=cur[x]?cur[x]:hd[x];i;i=nxt[i]) {
        cur[x]=i;
        if(c[i]&&lev[to[i]]==lev[x]+1) {
            if(f=Dfs(to[i],Min(mx,c[i])))
                return c[i]-=f,c[i^1]+=f,f;
        }
    }
    return 0;
}
void Dinic() {
    int f=0;
    while(Bfs(S,T)) {
        memset(cur,0,sizeof(cur));
        while(f=Dfs(S,Inf))Flow+=f;
    }
}
void DFS(int x,int b) {
    ex[x]=b;
    for(int i=hd[x];i;i=nxt[i])
        if(c[i]&&!ex[to[i]])
            DFS(to[i],b+1);
}
int main() {
    read(M),read(N);
    for(int i=1;i<=M;++i) {
        read(a[i]);sum+=a[i];
        while(ch!='\n') {
            read(b[i][++ct[i]]); b[i][ct[i]]+=M;
            Add(i,b[i][ct[i]],Inf);
            Add(b[i][ct[i]],i,0);
        }
    }
    for(int i=1;i<=N;++i) read(a[i+M]);
    S=N+M+1; T=S+1;
    for(int i=1;i<=M;++i) 
        Add(S,i,a[i]),Add(i,S,0);
    for(int i=M+1;i<=M+N;++i)
        Add(i,T,a[i]),Add(T,i,0);

    Dinic();    

    DFS(S,1);

    for(int i=1;i<=M;++i)
        if(ex[i]) printf("%d ",i); putchar(10);
    for(int i=1;i<=N;++i)
        if(ex[M+i]) printf("%d ",i); putchar(10);
    printf("%d\n",sum-Flow);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值