芝士:网络流

网络流不要问复杂度 \(\space\) ——Trymyedge

最大流

给定一个图,每一条边有一个流量,询问最大流量是多少

暴力

因为后面所说的三个算法都是基于暴力的改造,故在这里说一下暴力是很有必要的

考虑到最大的问题实际上是在于我们对于有限的入流不知道怎么分配给每一条边

所以我们对于每一条边加上反悔操作,即正向边上的流量表示还可以运输多少流量,反向边表示可以反悔多少流量,

之后,我们随便找到一条可以运输流量的路径,暴力加流量就可以了

可以将暴力当作调整法来理解

时间复杂度\(O(TLE)\)

EK

思路

因为路径只要能运输流量就可以了,但是EK算法强行要求这条路径必须是能运输流量的路径中长度最小的一条,边的长度初始为1

可以说明,时间复杂度为\(O(VE^2)\)

代码

因为之后的费用流是基于EK来写的,所以这里就不放EK代码了

实际上是因为笔者懒

isap

思路

同样的,isap也是基于路径选择来进行优化

\(e[i].u,e[i].v\),表示有一条从\(e[i].u\)连向\(e[i].v\)的单向边

t为终点

定义这样一个函数,\(d(i)\),满足\(\begin{cases}d(t)=0\\d[e[i].u]\le d[e[i].v]+1\end{cases}\)

然后寻找路径的时候,只在\(d[e[i].u]=d[e[i].v]+1\)的路径上寻找

时间复杂度$O(V^2E) $

\(d(i)\)函数是从终点开始的

代码

#include<iostream>
#include<cstring>
using namespace std;
struct node
{
    int to;
    int nxt;
    long long w;
}e[100005];
int n,m,s,t,cnt;
int h[5005];
int d[5005],nd[5005];//距离标记,距离标记为i的有多少个
void add(int u,int v,long long w)
{
    e[cnt].to=v;
    e[cnt].w=w;
    e[cnt].nxt=h[u];
    h[u]=cnt++;
}
long long dfs(int now,int t,long long inval)//开始节点,目标节点,入流
{
    long long s=0,mind=n+1;//已经使用的流量,分层
    if(now==t)
        return inval;
    for(int i=h[now];~i;i=e[i].nxt)
    {
        int v=e[i].to;
        if(!e[i].w)
			continue; 
        if(d[now]==d[v]+1)
        {
            long long del=(dfs(v,t,min(inval-s,e[i].w)));
            e[i].w-=del;
            e[i^1].w+=del;
            s+=del;
            if(s==inval)
                break;
			
        }
		if(d[v]<mind)
           	mind=d[v];   
    } 
    if(s==0)//无法增广
    {
        nd[d[now]]--;
        if(nd[d[now]]==0)
            d[t]=-1;    
        d[now]=mind+1;
        nd[d[now]]++;
    }
    return s;
}
long long isap(int s,int t)
{
    long long maxx=0;
    memset(d,0,sizeof(d));
    nd[0]=n;
    while(d[t]!=-1)
        maxx+=dfs(s,t,(1<<30));
    return maxx;
}
int main()
{
    memset(h,-1,sizeof(h));
    cin>>n>>m>>s>>t;
    for(int i=1,u,v,w;i<=m;i++)
    {
        cin>>u>>v>>w;
        add(u,v,w);
        add(v,u,0);
    }
    cout<<isap(s,t);
    return 0;
}

dinic

同样基于路径来优化

\(d(i)\)\(s\)\(i\)上最短路径的长度

我们只对\(d(e[i].u)+1=d(e[i].v)\)的边所组成的图上来寻找路径

时间复杂度\(O(V^2E)\)

\(d(i)\)是从起点开始

代码

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
struct node
{
    int to;
    int nxt;
    long long w;
}e[100005];
int n,m,s,t;
int cnt;
int h[205],cur[205];
int d[205];
void add(int u,int v,long long w)
{
    e[cnt]=(node){v,h[u],w};
   	h[u]=cnt++; 
}
bool bfs(int s,int t)
{
    memset(d,0x3f,sizeof(d));
    queue<pair<int,int> > q;
    q.push(make_pair(s,0));
    d[s]=0;
    while(!q.empty())
    {
        pair<int,int> t=q.front();
        q.pop();
        if(t.second>d[t.first])
            continue;
        for(int i=h[t.first];~i;i=e[i].nxt)
        {
            int v=e[i].to;
            if(!e[i].w)
                continue;
            if(d[v]>d[t.first]+1)
            {
                d[v]=d[t.first]+1;
                q.push(make_pair(v,d[v]));
            }
        }
    }
    return d[t]<=n;
}
long long dfs(int now,int t,int inval)
{
    long long s=0;
    if(now==t)
        return inval;
    for(int &i=cur[now];~i;i=e[i].nxt)
    {
        int v=e[i].to;
        if(d[now]+1!=d[v]||e[i].w==0)
            continue;
        int del=dfs(v,t,min(inval-s,e[i].w));
        e[i].w-=del;
        e[i^1].w+=del;
        s+=del;
        if(s==inval)
            return s;
    }
    return s;
}
long long dinic(int s,int t)
{
    long long ret=0;
    while(bfs(s,t))
    {
		for(int i=1;i<=n;i++)
			cur[i]=h[i];	
        ret=ret+dfs(s,t,(1<<30));
	}
	return ret;
}
int main()
{
	memset(h,-1,sizeof(h));
    cin>>n>>m>>s>>t;
    for(int i=1,u,v,w;i<=m;i++)
    {
        cin>>u>>v>>w;
        add(u,v,w);
        add(v,u,0);
    }
    cout<<dinic(s,t);
    return 0;
}

最大流最小割定理

如果有一个边集,将其中的所有边删去,那么图变成了两个互不连通的部分,我们称这种边集为割

最小割就是边权之和最小的割

最大流的值等于最小割的值

感性去理解,最小割决定了最大流

关于二分图

可以发现dinic的过程实际上是对整个图进行一个分层的操作

而二分图天然就是一个分好层的图,并且层数很少

如果在二分图上面用dinic跑最大匹配,时间复杂度为\(O(E\sqrt V)\)

最小费用最大流

定义每一条边有一个权值\(w\),如果经过的流量为\(f\),那么所需要的代价为\(f*w\)

思路

只需要将EK中的路径选择改成SPFA,用权值跑一个最短路,

因为流量一样的情况下,这样去分配流量目前一定是最优的

之后我们有反向边进行流量的调整,故整体也是最优的

时间复杂度\(O(玄学)\)

代码

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
struct node
{
    int to;
    int nxt;
    long long w;
    long long c;
}e[100005];
int cnt;
int h[5005];
int n,m,s,t;
int inq[5005],pre[5005],dis[5005];
bool used[100005];
void add(int u,int v,long long w,long long c)
{
    e[cnt]=(node){v,h[u],w,c};
    h[u]=cnt++;
}
bool spfa(int s,int t)
{
    memset(dis,0x3f,sizeof(dis));
    memset(used,0,sizeof(used));
    memset(inq,0,sizeof(inq));
    memset(pre,-1,sizeof(pre));
    queue<int> q;
    q.push(s);
    dis[s]=0;
    inq[s]=1;
    while(!q.empty())
    {
        int t=q.front();
        q.pop();
        inq[t]=0;
        for(int i=h[t];~i;i=e[i].nxt)
        {
            int v=e[i].to;
            if(e[i].w==0)
                continue;
            if(dis[v]>dis[t]+e[i].c)
            {
                dis[v]=dis[t]+e[i].c;
                pre[v]=i;
                if(!inq[v])
                {
                    inq[v]=1;
                    q.push(v);
                }
            }
        }
    }
    if(pre[t]==-1)
        return 0;
    for(int i=1;i<=n;i++)
        if(pre[i]!=-1)
            used[pre[i]]=1;
	return 1;
}
long long dfs(int now,int t,long long inval)
{
    long long s=0;
    if(now==t)
        return inval;
    for(int i=h[now];~i;i=e[i].nxt)
    {
        int v=e[i].to;
        if(!e[i].w)
            continue;
        if(used[i])
        {
            long long del=dfs(v,t,min(inval-s,e[i].w));
            e[i].w-=del;
            e[i^1].w+=del;
            s+=del;
            if(s==inval)
                break;
        }
    }
    return s;
}
pair<long long,long long> EK(int s,int t)
{
    pair<long long,long long> ans;
    while(spfa(s,t))
    {
        long long val=dfs(s,t,(1<<30));
        ans.first+=val;
        ans.second+=val*dis[t];
    }
    return ans;
}
int main()
{
	memset(h,-1,sizeof(h));
    cin>>n>>m>>s>>t;
    for(int i=1,u,v,w,c;i<=m;i++)
    {
        cin>>u>>v>>w>>c;
        add(u,v,w,c);
        add(v,u,0,-c);
    }
    pair<long long,long long> ans=EK(s,t);
    cout<<ans.first<<' '<<ans.second<<'\n';
    return 0;
}
posted @ 2021-02-05 15:53  loney_s  阅读(114)  评论(0)    收藏  举报