zoukankan      html  css  js  c++  java
  • 最大流最小割学习小记

    介绍

    网络流解决的是建模出来的流量网络的一些问题。
    一个流量网络中,会有一个源点和一个汇点。网络中的每一条边都有一个边权,为容量。
    想象每条边都是有流量限制的水管,水从源点源源不断地流入,再从汇点源源不断地流出。在这个过程中,每条水管中的流量限制都不能其限制。
    由此,衍生出了一些问题,比如汇点最大流量是多少,这就是最大流问题。

    最大流算法实现

    主流的最大流算法分两个大类,增广路算法和预推流算法。

    • 增广路:EK,dinic,SAP,ISAP
    • 预推流:Push-Relabel,HLPP
      只写了dini模板,其它有时间再补吧。

    增广路

    增广路就是,如果存在一条从源点到汇点的路,路上边剩余容量都不为0,那么就可以对这条路进行增广操作。
    增广路算法基本上就是从源点到汇点不停找增广路,直到找不到为止。累计下来的流量就是最大流。

    EK: dfs暴力找。
    dinic:每次dfs增广之前bfs一次给图分层,提高效率。
    SAP:用一个num数组来记录分层信息,dfs同时更新num,不用bfs那么多次。
    ISAP:相比SAP多一个gap优化。

    两个优化方向

    1. cur当前弧优化。一次dfs中一个边被增广过后就不会再被增广,所以用cur保存最后一次的边。(详见代码)
    2. 多路增广。如果当前结点流量没流完,那就走多几条路尽可能流完它。
    #define INF 0x3f3f3f3f
    using namespace std;
    
    const int N = 1e4 + 10;
    const int M = 2e5 + 10;
    const double eps = 1e5;
    
    struct edge {
        int ne, np, f;
    };
    edge ed[M];
    int head[N];
    int cur[N];
    int si = 2;
    int dis[N];
    int arr[N];
    ll cost[N];
    
    void init() {
        si = 2;
        memset(head, 0, sizeof head);
        memset(cur, 0, sizeof cur);
    }
    
    void add(int u, int v, int f) {
        ed[si] = edge{head[u], v, f};
        head[u] = si;
        cur[u] = head[u];
        si++;
    
        ed[si] = edge{head[v], u, 0};
        head[v] = si;
        cur[v] = head[v];
        si++;
    }
    
    bool bfs(int s, int t) {
        memset(dis, 0, sizeof dis);
        for(int i = 1; i <= t; i++) cur[i] = head[i]; // 当前弧优化,注意这里初始化是所有的点初始化
        queue<int> q;
        q.push(s);
        dis[s] = 1;
        while(!q.empty()) {
            int cur = q.front();
            q.pop();
            for(int i = head[cur]; i; i = ed[i].ne) {
                int nt = ed[i].np;
                if(dis[nt] || (!ed[i].f)) continue;
                dis[nt] = dis[cur] + 1;
                q.push(nt);
            }
        }
        return dis[t];
    }
    
    int dfs(int p, int t, int flo) {
        if(p == t) return flo;
        int delta = flo;
        
        for(int &i = cur[p]; i; i = ed[i].ne) {
            int nt = ed[i].np;
            if(dis[nt] == dis[p] + 1 && ed[i].f) {
                int d = dfs(nt, t, min(delta, ed[i].f));
                delta -= d;
                ed[i].f -= d; ed[i^1].f += d;
                if(delta == 0) break;
            }
        }
        return flo - delta;
    }
    
    ll dini(int s, int t) {
        ll ans = 0;
        while(bfs(s, t)) {
            ans += dfs(s, t, INF);
        }
        return ans; 
    }
    

    预推流

    本人菜+没时间,以后再补qwq

    一些小要点

    1. 最大流算法的反向边实现了反悔的操作。
    2. 可以用拆点的方法添加满足题目的一些限制条件,或代表点的限制。
    3. 添加一些点和边构建适应不同要求的模型,如有最小流限制等。

    最小割

    最小割是最大流的镜像问题。
    最小割即对一个流量网络,割掉一些边,使得从源点到不了汇点。问割掉的边最小的容量和是多少。
    事实上,最小割=最大流。
    通过这个事实,我们可以互相地证明增广路算法的正确性。

    证明
    记一个书上的证明

  • 相关阅读:
    【26】多任务学习
    【25】迁移学习
    【24】定位数据不匹配
    【23】不匹配数据划分的偏差和方差;判断问题所在的几个重要参数
    【22】在不同的划分上进行训练和测试
    【21】进行误差分析
    17-----vue-cli脚手架安装和webpack-simple模板项目生成
    15-----修饰符
    14-----表单输入绑定
    12-----指令系统介绍
  • 原文地址:https://www.cnblogs.com/limil/p/12897563.html
Copyright © 2011-2022 走看看