网络流

网络流

定义:

  • 点数 n,边数 m
  • 源点 s,汇点 t
  • 容量 w :允许通过的最大流
  • 流量 c :当前通过的流的大小
  • 流 f :从汇点流出的流量
  • 增广路 :从源点到汇点,边上剩余容量均为正的路径
  • 反悔边 :容量为 0,流量为原边的 -c,代表可退回的流量

Edmonds_Karp

朴素 bfs 找增广路,记录路径

时间复杂度 \(O(nm^2)\)

代码:

namespace Edmonds_Karp{
    int lst[N],f[N];
    queue<int> q;
    bool bfs(){
        memset(lst,-1,sizeof(lst));
        while(!q.empty()) q.pop();
        q.push(s);
        f[s]=INF;
        while(!q.empty()){
            int u=q.front();q.pop();
            if(u==t) break;
            REPG(i,g,u){
                int v=g.edge[i].to,w=g.edge[i].w,c=g.edge[i].c;
                if(lst[v]==-1 && w>c){
                    lst[v]=i;
                    f[v]=min(w-c,f[u]);
                    q.push(v);
                }
            }
        }
        return lst[t]!=-1;
    }
    LL solve(){
        LL ans=0;
        while(bfs()){
            ans+=f[t];
            for(int v=t;v!=s;v=g.edge[lst[v]^1].to){
                g.edge[lst[v]].c+=f[t];
                g.edge[lst[v]^1].c-=f[t];
            }
        }
        return ans;
    }
}

Dinic

bfs 分层图,在新图上 dfs 找增广路

dfs 需要当前弧优化(扔掉已经增广完的出边)

理论复杂度上界 \(O(n^2m)\)

单位容量图 \(O(m \min\{n^\frac{2}{3},m^\frac{1}{2}\})\)

代码:

namespace Dinic{
    int dep[N],cur[N];
    queue<int> q;
    bool bfs(){
        while(!q.empty()) q.pop();
        memset(dep,-1,sizeof(dep));
        memcpy(cur,g.head,sizeof(g.head));
        q.push(s);dep[s]=1;
        while(!q.empty()){
            int u=q.front();q.pop();
            if(u==t) break;
            REPG(i,g,u){
                int v=g.edge[i].to,w=g.edge[i].w,c=g.edge[i].c;
                if(dep[v]==-1 && w>c){
                    dep[v]=dep[u]+1;
                    q.push(v);
                }
            }
        }
        return dep[t]!=-1;
    }
    int dfs(int u,int f){
        if(u==t || !f) return f;
        int res=0;
        for(int i=cur[u];(~i) && res!=f;i=g.edge[i].nxt){
            cur[u]=i;
            int v=g.edge[i].to,w=g.edge[i].w,c=g.edge[i].c;
            if(dep[v]==dep[u]+1 && w>c){
                int nw=dfs(v,min(f-res,w-c));
                res+=nw;
                g.edge[i].c+=nw;
                g.edge[i^1].c-=nw;
            }
        }
        return res;
    }
    LL solve(){
        LL ans=0;
        while(bfs()) ans+=dfs(s,INF);
        return ans;
    }
}

最大流最小割定理

  1. 最大流 \(\ge\) 最小割
    如果小于,则说明没有满流,矛盾
  2. 最大流 \(\le\) 最小割
    如果大于,则说明 s 可以不通过割边到达 t,矛盾

综上,最大流 = 最小割

推论:对于每个最小割方案,存在最大流方案,其中每条割边都满流

最大权闭合子图

考虑最优为选所有正的,不选负的。

建图:

  • \(s\) 为源点,\(t\) 为汇点。
  • 对于依赖关系 \(x\to y\),连边 \((x,y,INF)\)
  • 对于节点,若代价为正,连边 \((s,i,v_i)\);反之,连边 \((i,t,-v_i)\)

若割掉与 \(s\) 相连的边,代表不选这个正的,损失 \(v_i\) 贡献;若割掉与 \(t\) 相连的边,代表选这个负的,付出 \(v_i\) 代价。

发现 \(s\)\(t\) 只会通过形如 \(s\to x\to y\to t\) 的边相连。

  • 如果保留 \((y,t)\),即不选 \(y\),则必然割掉 \((s,x)\),即不选 \(x\)
  • 如果割掉 \((y,t)\),即选 \(y\)\((s,x)\) 可割可不割,不受影响。

故此图的割满足\(y\) 是选 \(x\) 的必要条件

最大权为 所有正权值之和 \(-\) 最小割。

费用流

全称 最小费用最大流

每次找费用最小的增广路(bfs 改成 spfa)

基于 EK 的实现:

namespace EK{
    LL f[N];
    LL dis[N];
    int lst[N];
    bool vis[N];
    queue<int> q;
    bool SPFA(){
        while(!q.empty()) q.pop();
        memset(vis,0,sizeof(vis));
        rep(i,1,n) dis[i]=INF;
        rep(i,1,n) lst[i]=-1;
        dis[s]=0;q.push(s);vis[s]=1;
        f[s]=INF;
        while(!q.empty()){
            int u=q.front();q.pop();
            vis[u]=0;
            REPG(i,g,u){
                int v=g.edge[i].to,w=g.edge[i].w,fl=g.edge[i].f,c=g.edge[i].c;
                if(w>fl && dis[v]>dis[u]+c){
                    lst[v]=i;
                    f[v]=min(f[u],(LL)w-fl);
                    dis[v]=dis[u]+c;
                    if(!vis[v]) vis[v]=1,q.push(v);
                }
            }
        }
        return lst[t]!=-1;
    }
    void solve(){
        LL mxf=0,mnc=0;
        while(SPFA()){
            for(int i=lst[t];~i;i=lst[g.edge[i^1].to]){
                g.edge[i].f+=f[t];
                g.edge[i^1].f-=f[t];
            }
            mxf+=f[t];
            mnc+=f[t]*dis[t];
        }
        printf("%lld %lld\n",mxf,mnc);
    }
}

基于 Dinic 的实现:

namespace Dinic{
    LL dis[N],INF;
    int cur[N];
    bool inq[N];
    queue<LL> q;
    bool SPFA(){
        memset(dis,0x3f,sizeof(dis));INF=dis[0];
        memset(inq,0,sizeof(inq));
        memcpy(cur,g.head,sizeof(g.head));
        while(!q.empty()) q.pop();

        dis[s]=0,inq[s]=1,q.push(s);
        while(!q.empty()){
            LL u=q.front();q.pop();
            inq[u]=0;
            REPG(i,g,u){
                LL v=g.edge[i].to,w=g.edge[i].w,f=g.edge[i].f,c=g.edge[i].c;
                if(dis[v]>dis[u]+c && w>f){
                    dis[v]=dis[u]+c;
                    if(!inq[v]) q.push(v),inq[v]=1;
                }
            }
        }
        return dis[t]<INF;
    }

    LL dfs(LL u,LL flow){
        if(u==t || !flow) return flow;
        LL res=0;inq[u]=1;
        for(LL i=cur[u];(~i) && flow!=res;i=g.edge[i].nxt){
            LL v=g.edge[i].to,w=g.edge[i].w,f=g.edge[i].f,c=g.edge[i].c;
            cur[u]=i;
            if(!inq[v] &&(dis[v]==dis[u]+c) && w>f){
                LL nf=dfs(v,min(flow-res,w-f));
                res+=nf;
                g.edge[i].f+=nf;
                g.edge[i^1].f-=nf;
            }
        }
        inq[u]=0;
        return res;
    }

    void solve(){
        LL flow=0,cost=0;
        while(SPFA()){
            LL nf=dfs(s,INF);
            flow+=nf;
            cost+=nf*dis[t];
        }
        printf("%lld %lld\n",flow,cost);
    }
}

技巧

  1. 拆点
  2. 点数 & 边数优化
  3. 分层图
  4. 二分图相关
  5. 残量网络性质
    P4126 [AHOI2009] 最小割
  6. 最大权闭合子图
    P3749 [六省联考 2017] 寿司餐厅
    P2805 [NOI2009] 植物大战僵尸
  7. 退流
    P5295 [北京省选集训2019] 图的难题
    P3308 [SDOI2014] LIS

习题

P2065 [TJOI2011] 卡片

题意:

两行数,每次选上下两个不互质的数,问最多选几次

数据范围 \(T\le 100,n,m\le 500\)

建图:

  1. 直接把不互质都连起来 \(O(n^2)\),但是会 T(尽管看起来复杂度是 \(O(Tn^3)\)

key:无中生有质因子点

  1. 认为不互质的数是通过公共质因子相连的,这样边数是 \(O(n)\) 的,复杂度 \(O(Tn^2)\)

不能重复选 \(\to\) 所有边容量都是 1

P2763 试题库问题

题意:

n 个题,k 种属性,每个题可能有多种属性,要求每种属性有 \(x_i\) 道题。求方案

数据范围 \(k\le 20, n\le 10^3\)

建图:

每种属性一个点,把题和属性连起来

近似单位流量,复杂度大约 \(O(n^2k)\)

输出方案:

看每个属性的出边,哪些流量是 1

P2472 [SCOI2007] 蜥蜴

题意:

r 行 c 列网格,有一些蜥蜴,每次跳到欧几里得距离不超过 d 的节点,每个节点只能被经过 \(h_{i,j}\) 次,问最多可以有多少跳出边界

数据范围 \(r,c\le20,d\le4,h\le3\)

建图:

key:拆点-->限制经过次数

每个点拆成入点和出点,每条边是 (u出,v入)

有蜥蜴的入连源点,跳出边界的出连汇点

P2754 [CTSC1999] 家园 / 星际转移问题

k 个人从地球到月球,有 n 个中转站,m 条环形航线,每个时刻走到下个中转站。中转站容量无穷大,航线容量 \(h_i\)。问最早在什么时候全部到月球。

数据范围 \(n\le 13,m\le 20,k\le 50\)

key:按时间分层

注意细节/kk

  • 每个时刻在残量网络上跑 Dinic,应该是 res+=solve()
  • 0 时刻不能建中转站之间的边

P2765 魔术球问题

n 根柱子,依次放编号 \(1,2,3,\dots\) 的球

  • 每次只能在最上面放
  • 任意两个相邻球编号和为完全平方数

问最多能放多少个球

数据范围 \(n\le 55\)

转化为:k 个球,最少要用几根柱子。

能相邻的连一条边,得到 DAG。

key:DAG 最小路径覆盖=原图点数-二分图最大匹配

证明:每找到一条匹配边,就相当于合并两条路径

二分图最大匹配可以用网络流(s 连左,t 连右,单位容量)

P2766 最长不下降子序列问题

给定序列 \(a\),求:

  1. 最长不下降子序列长度 \(k\)
  2. 每个元素只用一次,最多选出多少长为 \(k\) 的不下降子序列
  3. 不限制 \(a_1\)\(a_n\),最多选出多少不同

key:按 \(f_i\) 分层

注意特判 \(n=1\)\(k=1\) /kk

P2057 [SHOI2007] 善意的投票 / [JLOI2010] 冠军调查

n 个人,每人一种立场(0/1),m 对朋友。

求:投票与自己立场不同 + 一对朋友投票不同 最小值

数据范围 \(n\le 300,m\le \frac{n(n-1)}{2}\)

建图:

s,t 分别连立场 0/1,朋友间连双向边。

去掉矛盾 \(\to\) 删边使 s,t 不连通

求最小割即可。

P2598 [ZJOI2009] 狼和羊的故事

\(n\times m\) 方格上有狼、羊、无人区,要修篱笆把它们分开,求最短长度。

数据范围 \(n,m\le 100\)

建图:\(s\to\)\(\to\) 无人区 \(\to\)\(\to t\)

求最小割。

key:无人区不需要连 s,t

原因:狼和羊被割开,相当于在中间修篱笆了,至于到底在无人区的哪里,并不关心。

P2774 方格取数问题

方格图,相邻的不能选,求最大价值和。

发现黑白染色后,同色一定不互斥,故互斥连边是二分图。

建图:\(s\to 黑 \to 白 \to t\),求最小割。

P4126 [AHOI2009] 最小割

给定 n 个点,m 条边的有向图,给定 s,t,对于每条边,询问:

  1. 是否可能在最小割中
  2. 是否一定在最小割中

key:残量网络 DAG

对于一条满流边 (u,v),如果残量网络上存在 u 到 v 的一条路径,显然可以破坏掉这条满流边,故这条边一定不在最小割中。

在残量网络上缩点,可行要求scc[u]!=scc[v],必须要求scc[u]==scc[s] && scc[v]==scc[t]

P5039 [SHOI2010] 最小生成树

给定 \(n\)\(m\) 边的边带权连通无向图,可以进行操作:

  • 选择一条边,把其他边的权值 \(-1\)

求使得边 \((A,B)\) 一定出现在最小生成树中,需要的最少操作次数。

转化操作:其他边 \(-1\) 就是自己 \(+1\)

key:一定出现 \(\to\) \(w\le W\) 的边不能让 \(A B\) 连通

只保留边权小于等于 \(W(A,B)\) 的边,跑最小割即可。

P3749 [六省联考 2017] 寿司餐厅

题面很长,故不写了。

key: 最大权闭合子图

  • 选一个区间,必选所有子区间(单向依赖)。
  • 贡献 & 代价都只算一次。
  • 有正有负。

打点补丁修正即可

题解

P2805 [NOI2009] 植物大战僵尸

第一道独立完成的网络流紫题!Congratulations!

key:拓扑 + 最大权闭合子图

后者是显然的(只能从右边进入 & 攻击)

但环 & 环上挂的树都不能走到,即为拓扑剩下的点

注意:拓扑方向是(先走 \(\to\) 后走),与网络流相反!

P4452 [国家集训队] 航班安排

n 个点,m 个请求,在 s 时刻从 a 出发,t 时刻到 b。初始 K 个飞机在起点,要求 T 时刻全回来。(任意点间)飞行有时间和代价,请求有收益。问最大收益。

数据范围:\(n,m\le 200,T\le 3000\)

错误思路:按时间分层建图(点数过多)

key:只考虑请求相关的点

在 x 时刻能到达就连边

注意起点要拆,因为有 K 架飞机。

P2045 方格取数加强版

PS:此方格取数非彼方格取数(bushi

\(n\times n\) 矩阵,从 (1,1) 向右 / 下走到 (n,n)。每个格子贡献不重复计算,一共走 K 次,求最大和。

数据范围:\(n\le 50\)

key:贡献不重复 \(\to\) 拆点连双边

\(\to\) 出建两条边 (1,val),(INF,0)

P2050 [NOI2012] 美食节

P2053 [SCOI2007] 修车 的数据加强版。

n 种菜(每种点 \(p_i\) 份),m 个厨师。每个厨师做菜时间不同,求最小总等待时间。

做每道菜贡献的等待时间,与这是第几道菜有关。拆点,厨师 i 做的倒数 第 k 道菜,等待时间要 \(\times k\),代表有 \(k\) 个人要等待。

如果直接建图就是 P2053 [SCOI2007] 修车,在本题会拿到 \(60\) pts 的好成绩

key:动态开点

发现有很多点是不会被用到的,故只需在第 k 个点被用到之后建第 k+1 个点,判断平凡

点数 \(O(n+p)\) 边数 \(O(np)\),可以通过本题。

P2604 [ZJOI2010] 网络扩容

有向图,容量 c,扩容费用 w。求:

  1. 不扩容最大流
  2. 将最大流增加 k 的最小扩容费用。

跑一遍最大流,然后给每条边对应再建一条 (INF,c) 的边,跑费用流即可。

P2770 航空路线问题

已知某些城市间有航线,求一条途径城市最多的路线满足:

  1. 从最西端城市出发,单向从西到东到达最东端,再从东到西回去。
  2. 除起点外,任何城市只能经过一次

输出最大通过数及方案

拆点,起终点容量为 2,其他为 1,费用均为 1,最大费用最大流。

trick:两点间连边容量 inf(拆点已经限制过了,避免特判 (1,n))

输出方案平凡。

P5295 [北京省选集训2019] 图的难题

给定无向图,问能否染成两个颜色,分别内部无环(森林)。

数据范围 \(n\le 2000,m\le 4000\)

结论:可以染成当且仅当对于任意导出子图,\(|E|\le 2|V|-2\)

证明很长,与网络流无关,故略过。

转化条件,相当于要求 \(\max\{|E|-2|V|\}\le -2\)

显然是最大权闭合子图,选一条边必须选两个端点。

注意,要求子图非空,需要枚举一个必须选的点,在网络中删去这个点再跑最大流。

key: 退流

删去这个点本质就是将 \((u,T)\) 的容量置为 0。

若要删去边 \((u,v)\),则从 \(u\)\(S\) 跑最大流,再从 \(T\)\(v\) 跑最大流。
最后将容量置为 0,再重新跑 \(S\)\(T\) 的最大流。

本质:把流到 \((u,v)\) 边的流量通过反向边退回去,下次跑的时候换条路流。
感性理解,流到 \((u,v)\) 边的流量不会太多,退回去后跑 dinic 的复杂度也不会很高,所以是很有效的优化。

posted @ 2024-11-13 16:54  Cindy_Li  阅读(53)  评论(0)    收藏  举报