网络流全模板简单入门

3 篇文章 0 订阅

网络流

最大流

增广路思路

EK算法
/*
    bfs + update
    时间复杂度O(nm^2)
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const ll inf =0x3f3f3f3f3f3f;
ll h[N],e[N],ne[N],val[N],idx;
int pre[N];
bool vis[N];
ll incf[N];//流过
int n,m,s,t;
int ans;
ll maxflow;
//正向和反向建边
void add(int a,int b,int c)
{
    e[idx] = b,ne[idx] = h[a],val[idx] = c,h[a] = idx++;
    e[idx] = a,ne[idx] = h[b],val[idx] = 0,h[b] = idx++;
}

//跑增广路
//如果存在增广路
bool bfs()
{
    memset(vis,0,sizeof vis);
    queue<int> q;
    q.push(s);
    vis[s] = 1;
    incf[s] = inf;
    while(q.size())
    {
        int u = q.front();
        q.pop();
        for(int i=h[u];~i;i=ne[i])
            if(val[i])
            {
                int j = e[i];
                if(vis[j])continue;
                incf[j] = min(incf[u],val[i]);
                pre[j] = i;
                q.push(j);
                vis[j] = 1;
                if(j==t)return 1;
            }
    }
    return 0;
}

//减容量 并且反向边加容量
void update()
{
    int x = t;
    while(x!=s)
    {
        int i = pre[x];
        val[i] -= incf[t];
        val[i^1] += incf[t];
        x = e[i^1];
    }
    maxflow += incf[t];
}





int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m>>s>>t;
    int a,b,c;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }

    while(bfs())update();

    cout<<maxflow<<endl;
    return 0;
}
Dinic算法
/*
    时间复杂度O(n^2m)
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const ll inf =0x3f3f3f3f3f3f;
ll h[N],e[N],ne[N],val[N],idx;
int d[N];
ll incf[N];//流过
int n,m,s,t;
int ans;
ll maxflow;
//正向和反向建边
void add(int a,int b,int c)
{
    e[idx] = b,ne[idx] = h[a],val[idx] = c,h[a] = idx++;
    e[idx] = a,ne[idx] = h[b],val[idx] = 0,h[b] = idx++;
}

//跑增广路
//如果存在增广路
bool bfs()
{
    memset(d,0,sizeof d);
    queue<int> q;
    q.push(s);d[s] = 1;
    while(q.size())
    {
        int u = q.front();
        q.pop();
        for(int i=h[u];~i;i=ne[i])
            if(val[i]&&!d[e[i]])
            {
                q.push(e[i]);
                d[e[i]] = d[u] + 1;
                if(e[i]==t)return 1;//可以到汇点 还可以跑增广路
            }
    }
    return 0;//到不了汇点 不能跑增广路
}


//
//1.优化 并行增广
//2.去除 增广完毕的点 
ll dinic(int u,ll flow)//flow 总流
{
    if(u==t)return flow;
    ll rest = flow,k;
    for(int i=h[u];~i&&rest;i=ne[i])
        if(val[i]&&d[e[i]]==d[u]+1)
        {
            int j = e[i];
            k = dinic(j,min(rest,val[i]));
            if(!k)d[j] = 0;//去除增广完毕的点
            val[i] -= k;
            val[i^1] += k;
            rest -= k;
        }
    return flow - rest;
}





int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m>>s>>t;
    int a,b,c;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    ll flow = 0;
    while(bfs())
        while(flow=dinic(s,inf))maxflow += flow;

    cout<<maxflow<<endl;
    return 0;
}
ISAP算法
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e5+100;
const ll INF = 1e18+10;
ll cur[N],h[N],e[N],ne[N],val[N],idx;//邻接表
int n,m,s,t;
int d[N];//每一个点的层次 t=1
int gap[N];//gap数组优化 当某一层为空的时候那就是出现了断层必不存在增广路
ll max_flow;
inline void add(int a,int b,ll c)
{
    e[idx] = b,ne[idx] = h[a],val[idx] = c,h[a] = idx++;
    e[idx] = a,ne[idx] = h[b],val[idx] = 0,h[b] = idx++; 
}

//反向bfs求层次图
void inver_bfs()
{
    queue<int> q;
    q.push(t);
    ++gap[d[t]=1];
    while(q.size())
    {
        int u = q.front();
        q.pop();
        for(int i=h[u];~i;i=ne[i])
        {
            int j = e[i];
            if(d[j])continue;
            q.push(j);
            gap[d[j]=d[u]+1]++;
        }
    }
}


ll dfs(int u,ll flow)
{
    if(flow==0)return 0;
    if(u==t)
    {
        max_flow += flow;
        return flow;
    }
    ll used = 0;
    for(ll &i=cur[u];~i;i=ne[i])
    {
        int j = e[i];
        if(d[j]==d[u]-1)
        {
            ll k = dfs(j,min(flow-used,val[i]));
            if(k)val[i]-=k,val[i^1]+=k,used+=k;
            if(used==flow)return flow;
        }
    }
    //出现断层则break :令d[s]=n+1;
    //改点在这一层的边都遍历完了,直接令其高度加1
    (--gap[d[u]])?(++gap[++d[u]]):d[s] = n+1;
    return used;
}


ll isap()
{
    //先反向建图一次
    inver_bfs();
    while(d[s]<=n)
    {
        memcpy(cur,h,sizeof(h));
        dfs(s,INF);
    }
    return max_flow;
}

int main()
{
    cin>>n>>m>>s>>t;
    int a,b;
    ll c;
    memset(h,-1,sizeof h);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%lld",&a,&b,&c);
        add(a,b,c);
    }

    cout<<isap()<<endl;
    return 0;
}
最高标志预流推进算法
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 2e4+10;
const int M = 4e5+10;
const int INF = 0x3f3f3f3f;
ll  edge[M];
int h[N],e[M],idx,ne[M];
int n,m,s,t;
ll reser[N];//存储每一个点还剩余多少的流量
int gap[N];
int hi[N];
bool vis[N];
priority_queue<int,vector<int>,greater<int>> hq;

inline void add(int a,int b,ll c)
{
    e[idx] = b,ne[idx] = h[a],edge[idx] = c,h[a] = idx++;
    e[idx] = a,ne[idx] = h[b],edge[idx] = 0,h[b] = idx++; 
}

//给所有的点贴上高度标签
inline bool bfs()
{
    memset(hi,0x3f,sizeof(int)*n);
    hi[t] = 0;
    queue<int> q;
    q.push(t);
    while(q.size())
    {
        int u = q.front();
        q.pop();
        for(int i=h[u];~i;i=ne[i])
        {
            int j  = e[i];
            if(edge[i^1]&&hi[j]>hi[u]+1)
            {
                hi[j] = hi[u] + 1;
                q.push(j);
            }
        }
    }
    return hi[s]!=INF;
}

inline void push(int u)
{
    for(int i=h[u];~i;i=ne[i])
    {
        int j = e[i];
        if(edge[i]&&(hi[j]+1==hi[u]))
        {
            ll  k = min(reser[u],edge[i]);
            edge[i] -= k;
            edge[i^1] += k;
            reser[u] -= k;
            reser[j] += k;
            if((j!=s)&&(j!=t)&&(!vis[j]))
            {
                hq.push(j);
                vis[j] = 1;
            }
            if(!reser[u])break;
        }
    }
}

//重贴标签使其可以流向最低的点
inline void relabel(int u)
{
    hi[u] = INF;
    for(int i=h[u];~i;i=ne[i])
    {
        int j = e[i];
        if(edge[i]&&(hi[j]+1<hi[u]))hi[u] = hi[j]+1;
    }
}

inline ll hlpp()
{
    if(!bfs())return 0;
    
    hi[s] = n;//给源点贴上最高标签
    
    memset(gap,0,sizeof(gap));
    for(int i=1;i<=n;i++)
        if(hi[i]!=INF)gap[hi[i]]++;
        
    for(int i=h[s];~i;i=ne[i])
    {
        int j = e[i];
        if(int f = edge[i])
        {
            edge[i] -= f;
            edge[i^1] += f;
            reser[s] -= f;
            reser[j] += f;
            if(j!=s&&j!=t&&!vis[j])
            {
                hq.push(j);
                vis[j] = 1;
            }
        }
    }

    while(hq.size())
    {
        int t = hq.top();
        hq.pop();
        vis[t] = 0;
        push(t);
        if(reser[t])
        {
            //如果出现断层的化 那么h[i]>h[v]的点都不能向下传递到汇点
            //把他们的高度置为 n+1 全部送回源点
            gap[hi[t]]--;
            if(!gap[hi[t]])
            {
                for(int v=1;v<=n;v++)
                {
                    if(v!=t&&v!=s&&(hi[v]>hi[t])&&(hi[v]<n+1))
                    {
                        hi[v] = n+1;
                    }
                }
            }
            //重贴标签然后传递流
            relabel(t);gap[hi[t]]++;
            hq.push(t);vis[t] = 1;
        }
    }
    return reser[t];
}

int main()
{
    scanf("%d%d%d%d",&n,&m,&s,&t);
    memset(h,-1,sizeof h);
    int a,b;
    ll c;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%lld",&a,&b,&c);
        add(a,b,c);
    }
    
    cout<<hlpp()<<endl;
    return 0;
}

费用流

模板题:K取方格数

    //建图 方式 
    //1.裂点 成 入点 和 出点
    //2.在入点 和 出点上建立 1条带权容量为1的边 和 一条权为0容量为k-1的边
    //源点是1   汇点是 n + n
    //建边的方式:
    //
    //然后跑一遍最大费用最大流算法即可

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <string>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
ll h[N],e[N],val[N],ne[N],edge[N],idx;
int pre[N];
bool st[N];
int d[N];
ll incf[N];//每 点 的剩余容量
int s,t;
ll maxflow;
ll ans;
int n,k;

int num(int i,int j,int k)
{
     return (i-1)*n + j + k*n*n;
}

void add(int a,int b,int z,int c)//z表示容量 c表示边权
{
    e[idx] = b,edge[idx] = z,val[idx] = c,ne[idx] = h[a],h[a]=idx++;
    e[idx] = a,edge[idx] = 0,val[idx] = -c,ne[idx] = h[b],h[b]=idx++;
}

//找到最短路
bool spfa()
{
    queue<int> q;
    memset(d,0xcf,sizeof d);
    memset(st,0,sizeof st);
    q.push(s);
    d[s] = 0;
    st[s] = 1;
    incf[s] = 1 << 30;
    while(q.size())
    {
        int u = q.front();
        q.pop();
        st[u] = 0;
        for(int i=h[u];~i;i=ne[i])
            if(edge[i])
            {
                int j = e[i];
                if(d[j]<d[u]+val[i])
                {
                    d[j] = d[u] + val[i];
                    incf[j] = min(incf[u],edge[i]);
                    pre[j] = i;
                    if(!st[j])st[j] = 1,q.push(j);
                } 
            }
    }  
    if(d[t]==0xcfcfcfcf)return false;
    return true; 
}

//更新残余网络
int update()
{
    int x = t;
    while(x!=s)
    {
        int i = pre[x];
        edge[i] -= incf[t];
        edge[i^1] += incf[t];
        x = e[i^1];
    }
    maxflow += incf[t];
    ans += d[t]*incf[t];
}


int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>k;
    s = 1, t = 2*n*n;

    int a;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            cin>>a;
            add(num(i,j,0),num(i,j,1),1,a);
            add(num(i,j,0),num(i,j,1),k-1,0);
            if(i<n)add(num(i,j,1),num(i+1,j,0),k,0);
            if(j<n)add(num(i,j,1),num(i,j+1,0),k,0);   
        }
    
    while(spfa())update();

    cout<<ans<<endl;
    return 0;
}

最小割

最大流最小割定理:

​ 最大流==最小割

求割边的数量:令每条边的容量为1即可 跑最大流算法即可

经典问题:二选其一

有n个物品 物品i放进集合A有代价a,放进有代价b,如果物品i,j不放在一个集合有代价c

那么建图 S源点代表集合A T汇点表示集合B

每一个物品 1.S向其连一条有向边容量为a

​ 2.物品向T连一条有向边容量为b

​ 3.物品u,v之间连一条 容量为w的双向边

上下界网络流

无源汇上下界可行流

无源汇网络是一个循环流

必要流:所有边的下界流量之和

对于每一个点 流经它的必要流不一定满足流量守恒,定义 A [ i ] = f 入 − f 出 A[i] = f入 - f出 A[i]=ff

如果$A[i]小于0 那么我们就建立一条 i 点到虚拟汇点T’的一条流量为A[i]的边 如果流量大于0反之 $

其他边的容量为 f 上 界 − f 下 界 f上界 - f下界 ff 这种边称之为附加边 建立的过程中给每一条附加边一个编号 方便后面统计附加流的大小 所构成的流成为附加流

在这样的网络上从虚拟源点S’ 到虚拟汇点T’ 跑一次最大流

如果最大流 等于 从虚拟源点加的边之和 那么可行流存在(我们加的边对于S’和T‘来说是流量守恒的,显然)

A n s = 附 加 流 + 必 要 流 Ans = 附加流 + 必要流 Ans=+

模板题:

https://loj.ac/problem/115

/*
    算法实现:见博客解析
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
const ll inf =0x3f3f3f3f3f3f;
ll h[N],e[N],ne[N],val[N],idx;
int d[N];
ll incf[N];//流过
int n,m,s,t;
int ans;
ll maxflow;
//正向和反向建边
void add(int a,int b,int c)
{
    e[idx] = b,ne[idx] = h[a],val[idx] = c,h[a] = idx++;
    e[idx] = a,ne[idx] = h[b],val[idx] = 0,h[b] = idx++;
}

//跑增广路
//如果存在增广路
bool bfs()
{
    memset(d,0,sizeof d);
    queue<int> q;
    q.push(s);d[s] = 1;
    while(q.size())
    {
        int u = q.front();
        q.pop();
        for(int i=h[u];~i;i=ne[i])
            if(val[i]&&!d[e[i]])
            {
                q.push(e[i]);
                d[e[i]] = d[u] + 1;
                if(e[i]==t)return 1;//可以到汇点 还可以跑增广路
            }
    }
    return 0;//到不了汇点 不能跑增广路
}

//1.优化 并行增广
//2.去除 增广完毕的点 
ll dinic(int u,ll flow)//flow 总流
{
    if(u==t)return flow;
    ll rest = flow,k;
    for(int i=h[u];~i&&rest;i=ne[i])
        if(val[i]&&d[e[i]]==d[u]+1)
        {
            int j = e[i];
            k = dinic(j,min(rest,val[i]));
            if(!k)d[j] = 0;//去除增广完毕的点
            val[i] -= k;
            val[i^1] += k;
            rest -= k;
        }
    return flow - rest;
}

int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m>>s>>t;
    int a,b,c;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    
    ll flow = 0;
    while(bfs())
        while(flow=dinic(s,inf))maxflow += flow;

    cout<<maxflow<<endl;
    return 0;
}

有源汇上下界可行流

思路:在S和T之间加一条上界为00 下界为 0 的边即可
//照抄无源汇的代码就🆗了
//就是要加条边就行

有源汇上下界最大流

思路:

A n s = f l o w 1 ( 可 行 解 ) + f l o w 2 ( 在 原 汇 点 和 原 源 点 之 间 的 最 大 流 ) Ans = flow1(可行解) + flow2(在原汇点和原源点之间的最大流) Ans=flow1+flow2

证明过于玄学(其实就是我不会)

注意:算法实现的过程中注意把虚拟源点和虚拟汇点之间的超级边(上界是inf,下界是0)删去。

模板题:https://loj.ac/problem/116
/*



*/

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <queue>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
int h[N], e[N], ne[N], idx;
ll low[N];
ll val[N];
int id[N];  //记录每一条边的残余网络中的id号码
int lo[N];
int A[N];
int n, m, s, t;
int d[N];
int ss, tt;  //虚拟源点和虚拟汇点

void add(int a, int b, ll c) {
    e[idx] = b, ne[idx] = h[a], val[idx] = c, h[a] = idx++;
    e[idx] = a, ne[idx] = h[b], val[idx] = 0, h[b] = idx++;
}

//跑增广路
//如果存在增广路
bool bfs(int ss, int tt) {
    memset(d, 0, sizeof d);
    queue<int> q;
    q.push(ss);
    d[ss] = 1;
    while (q.size()) {
        int u = q.front();
        q.pop();
        for (int i = h[u]; ~i; i = ne[i])
            if (val[i] && !d[e[i]]) {
                q.push(e[i]);
                d[e[i]] = d[u] + 1;
                if (e[i] == tt)
                    return 1;  //可以到汇点 还可以跑增广路
            }
    }
    return 0;  //到不了汇点 不能跑增广路
}

//
// 1.优化 并行增广
// 2.去除 增广完毕的点
ll dinic(int u, int tt, ll flow)  // flow 总流
{
    if (u == tt)
        return flow;
    ll rest = flow, k;
    for (int i = h[u]; ~i && rest; i = ne[i])
        if (val[i] && d[e[i]] == d[u] + 1) {
            int j = e[i];
            k = dinic(j, tt, min(rest, val[i]));
            if (!k)
                d[j] = 0;  //去除增广完毕的点
            val[i] -= k;
            val[i ^ 1] += k;
            rest -= k;
        }
    return flow - rest;
}

int main() {
    memset(h, -1, sizeof h);
    cin >> n >> m >> s >> t;
    int a, b, up;
    for (int i = 1; i <= m; i++) {
        scanf("%d%d%d%d", &a, &b, &lo[i], &up);
        A[a] -= lo[i];
        A[b] += lo[i];
        id[i] = idx;
        add(a, b, up - lo[i]);
    }

    //建立平衡边
    //设置虚拟源点
    ll sum_ss = 0;
    ss = n + 1;
    tt = n + 2;

    add(t, s, INF);

    for (int i = 1; i <= n; i++) {
        if (A[i] < 0)
            add(i, tt, -A[i]);
        if (A[i] > 0) {
            sum_ss += A[i];
            add(ss, i, A[i]);
        }
    }
    add(t, s, INF);
    ll flow1 = 0;
    ll flow2 = 0;
    ll flow = 0;

    while (bfs(ss, tt))
        while (flow = dinic(ss, tt, INF)) flow1 += flow;

    if (flow1 == sum_ss)  //存在可行流flow1
    {
        //最大流就是再跑一次
        while (bfs(s, t))
            while (flow = dinic(s, t, INF)) flow2 += flow;
        cout << flow2 << endl;
    } else
        cout << "please go home to sleep" << endl;

    return 0;
}

有源汇上下界最小流

删除所有的附加边后退回流量即可!

好像有点麻烦算了不码了

网络流24题

待我写!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值