网络流总结

dinic算法

算法流程:
1.每条边addE一条反向边
2.需要多次bfs更新图,进行分层
3.多路增广,只走下一层的路径
复杂度:O(n2m)
代码模版:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
int n,m,s,t;
LL ans;
int cnt=1;
vector<int> g[210];
struct edge{
	int from,to; LL flow,cap;
}e[40010];
void addE(int u,int v,LL z){
	cnt++;
	e[cnt].from=u; e[cnt].to=v; e[cnt].cap=z; g[u].push_back(cnt);
}

int vis[201],dep[201];

int bfs(){
	queue<int> q;
	memset(vis,0,sizeof(vis));
	memset(dep,0,sizeof(dep));
	q.push(s); vis[s]=1;
	while(!q.empty()){
		int u=q.front(); q.pop();
		for(int i=0; i<g[u].size(); i++){
			edge t=e[g[u][i]];
			int v=t.to;
			if(vis[v] || t.cap == 0) continue;
			vis[v]=1; dep[v]=dep[u]+1;
			q.push(v);
		}
	}
	return dep[t];
}
LL dinic(int u,LL in){
	if(u == t) return in;
	LL out=0;
	for(int i=0; i<int( g[u].size() ); i++){
		int t=g[u][i];
		int v=e[t].to;
		if(dep[v] == dep[u]+1 && e[t].cap){
			LL res=dinic(v,min(in,e[t].cap));
			e[t].cap-=res;
			e[t^1].cap+=res;
			in-=res;
			out+=res;
		}
	}
	if(out == 0)
		dep[u]=0;
	return out;
}
int main(){
//	freopen("d.in","r",stdin);
	scanf("%d %d %d %d", &n, &m, &s, &t);
	for(int i = 1; i <= m; ++i) {
		int u, v; LL w;
		scanf("%d %d %lld", &u, &v, &w);
		addE(u, v, w);
		addE(v, u, 0);
	}
	while(bfs())
		ans+=dinic(s,1e18);
	cout<<ans;
	return 0;
}

ISAP

算法流程:
(1)概述:算法基于这样的一个事实:每次增广之后,任意结点到汇点(在残余网络中)的最短距离都不会减小。这样,我们可以利用d[i]表示结点i到汇点的距离的下界。然后再增广过程当中不断地修改这个下界。增广的时候和Dinic算法类似,只允许沿着d[i]==d[j]+1的弧(i,j)走。
不难证明,d[i]满足两个条件:(1)d[t]=0;(2)对任意的弧(i,j) d[i]≤d[j]+1。因为最坏的情况就是s到t是一条链,此时等号成立。因此,当d[s]≥n时,残余网络中不存在s-t路。

那么与Dinic算法类似,事先逆向bfs,找增广的过程就是沿着“允许弧”(即满足f< c且d[i]==d[j]+1的弧)往前走。(称为“前进”)。如果向前走不动了,那么就要考虑原路返回(称为“撤退”)。此时把d[i]修改为min{d[j]}+1即可。因为要满足d函数的条件(2)。修改后,原来的i值的个数就减少一个,而新i值的个数多一个。在程序中,用num数组来保存所有距离的个数,当把距离值从x修改为y时,num[x]–,num[y]++即可,然后检查num[x]是否为0,如果是0,那么s-t不连通,算法终止。原因显而易见:比如s-t的距离是3,如果距离为2的情况都已经没了,更别提走到距离为1的点了。这就是所谓的“gap优化”。

通过之前的分析,在数据结构方面,该算法只比Dinic算法的数据结构多了两个数组:用于记录父边以便于撤退的数组p,以及标记距离个数的数组num。增广的时候分为两步,第一步逆推求出可改进量a(即残余量的最小值);第二步再逆推一遍,进行增广。主过程中,x走到汇点时增广。

#include<bits/stdc++.h>
using namespace std;

const int N = 10 + 1e4;
const int M = 10 + 1e5 + N;
const int inf = 0x3f3f3f3f;

int n, m, s, t, tot = 1;
int head[M<<1], d[N], h[N], g[N], pre[N];

struct node
{
	int to, next, cap, flow;
}e[M<<1];

void add(int a, int b, int c)
{
	e[++tot].cap = c; e[tot].flow = 0; e[tot].to = b; e[tot].next = head[a]; head[a] = tot;
	e[++tot].cap = 0; e[tot].flow = 0; e[tot].to = a; e[tot].next = head[b]; head[b] = tot;
}

void set_h(int t, int n)
{
	queue<int> q;
	memset(h, -1, sizeof h);
	memset(g, 0, sizeof g);
	h[t] = 0;
	q.push(t);
	while(q.size())
	{
		int u = q.front(); q.pop();
		++g[h[u]];
		for(int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].to;
			if(h[v] == -1)
			{
				h[v] = h[u] + 1;
				q.push(v);
			}
		}
	}
}

int ISAP(int s, int t, int n)
{
	set_h(t, n);
	int ans = 0, u = s, d;
	while(h[s] < n)
	{
		int i = head[u];
		if(u == s) d = inf;
		for(; ~i; i = e[i].next)
		{
			int v = e[i].to;
			if(e[i].cap > e[i].flow && h[u] == h[v]+1)
			{
				u = v;
				pre[v] = i;
				d = min(d, e[i].cap-e[i].flow);
				if(u == t)
				{
					while(u != s)
					{
						int j = pre[u];
						e[j].flow += d;
						e[j^1].flow -= d;
						u = e[j^1].to;
					}
					ans += d;
					d = inf;
				}
				break;
			}
		}
		if(i == -1)
		{
			if(--g[h[u]] == 0) break;
			int hmin = n-1;
			for(int j = head[u]; ~j; j = e[j].next)
				if(e[j].cap > e[j].flow)
					hmin = min(hmin, h[e[j].to]);
			h[u] = hmin+1;
			++g[h[u]];
			if(u != s) u = e[pre[u]^1].to;//ÍËÒ»²½£¬Ñظ¸±ß·µ»Ø
		}
	}
	return ans;
}

int main()
{
	memset(head, -1, sizeof head);
	cin>>n>>m>>s>>t;
	for(int i = 0; i < m; i++)
	{
		int a, b, c; cin>>a>>b>>c;
		add(a, b, c);
	}
	cout<<ISAP(s, t, n);
	return 0;
}

无源汇

无源汇上下界可行流

在这里插入图片描述
问题转换:题目问是否存在满足容量限制的可行流,相当于问新网络里的最大流是否可以达到源点的出边容量之和。

#include<bits/stdc++.h>
using namespace std;

const int N = 2000 + 10;
const int M = (100210 + N) + 10;
const int inf = 0x3f3f3f3f;

int n, m, s, t, tot = 1, tott;
int head[M<<1], d[N], L[M<<1], cur[N], delta[N];

struct Edge
{
	int to, next, cap, flow;
}e[M<<1];

void add(int a, int b, int c, int d)
{
	e[++tot].cap = d-c; e[tot].flow = 0; e[tot].to = b; e[tot].next = head[a]; head[a] = tot;
	L[tot] = c;
	e[++tot].cap = 0; e[tot].flow = 0; e[tot].to = a; e[tot].next = head[b]; head[b] = tot; 
}

bool bfs(int s, int t)
{
	memset(d, 0, sizeof d);
	queue<int> q;
	d[s] = 1;
	cur[s] = head[s];
	q.push(s);
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		for(int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].to;
			if(!d[v] && e[i].cap > e[i].flow)
			{
				d[v] = d[u]+1;
				cur[v] = head[v];
				if(v == t) return 1;
				q.push(v);
			}
		}
	}
	return 0;
}

int dfs(int u, int flow, int t)
{
	if(u == t) return flow;
	int res = flow;
	for(int i = cur[u]; ~i && res; i = e[i].next)
	{
		cur[u] = i;
		int v = e[i].to;
		if(d[v] == d[u]+1 && e[i].cap > e[i].flow)
		{
			int k = dfs(v, min(res, e[i].cap-e[i].flow), t);
			if(!k) d[v] = 0;
			e[i].flow += k;
			e[i^1].flow -= k;
			res -= k;
		}	
	}
	return flow-res;
}

int dinic()
{
	int maxflow = 0, flow;
	while(bfs(s, t)) maxflow += dfs(s, inf, t);
	return maxflow;
}

int main()
{
	memset(head, -1, sizeof head);
	cin>>n>>m;
	s = 0;
	t = n+1;
	for(int i = 0; i < m; i++)
	{
		int a, b, c, d; cin>>a>>b>>c>>d;
		add(a, b, c, d);
		delta[a] -= c;
		delta[b] += c;
	}
	for(int i = 1; i <= n; i++)
	{
		if(delta[i] > 0)
		{
			add(s, i, 0, delta[i]);
			tott += delta[i];
		}
		else if(delta[i] < 0)
			add(i, t, 0, -delta[i]);
	}
	if(dinic() != tott)
	{
		cout<<"NO";
		return 0;
	}
	// 输出结果为可行流中每条边的流量
	cout<<"YES"<<endl;
	for(int i = 2; i < m*2+2; i += 2)
		cout<<e[i^1].cap-e[i^1].flow+L[i]<<endl;
	
	return 0;
}



有源汇

有源汇上下界可行流

算法详解:为了使源汇点满足流量守恒,所以从汇点t向源点s连一条下界为0上界为无穷大的边,相当于把从源点s流出的流量再流回来。在这样的图中使用无源汇上下界可行流算法求出一个可行流,拆掉从汇点tt到源点ss的边就得到一个有源汇上下界可行流。

#include<bits/stdc++.h>
using namespace std;

const int N = 2000 + 10;
const int M = (100210 + N) + 10;
const int inf = 0x3f3f3f3f;

int n, m, s, t, ss, tt, tot = 1, tott;
int head[M<<1], d[N], L[M<<1], cur[N], delta[N];

struct Edge
{
	int to, next, cap, flow;
}e[M<<1];

void add(int a, int b, int c, int d)
{
	e[++tot].cap = d-c; e[tot].flow = 0; e[tot].to = b; e[tot].next = head[a]; head[a] = tot;
	L[tot] = c;
	e[++tot].cap = 0; e[tot].flow = 0; e[tot].to = a; e[tot].next = head[b]; head[b] = tot; 
}

bool bfs(int s, int t)
{
	memset(d, 0, sizeof d);
	queue<int> q;
	d[s] = 1;
	cur[s] = head[s];
	q.push(s);
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		for(int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].to;
			if(!d[v] && e[i].cap > e[i].flow)
			{
				d[v] = d[u]+1;
				cur[v] = head[v];
				if(v == t) return 1;
				q.push(v);
			}
		}
	}
	return 0;
}

int dfs(int u, int flow, int t)
{
	if(u == t) return flow;
	int res = flow;
	for(int i = cur[u]; ~i && res; i = e[i].next)
	{
		cur[u] = i;
		int v = e[i].to;
		if(d[v] == d[u]+1 && e[i].cap > e[i].flow)
		{
			int k = dfs(v, min(res, e[i].cap-e[i].flow), t);
			if(!k) d[v] = 0;
			e[i].flow += k;
			e[i^1].flow -= k;
			res -= k;
		}	
	}
	return flow-res;
}

int dinic()
{
	int maxflow = 0, flow;
	while(bfs(s, t)) maxflow += dfs(s, inf, t);
	return maxflow;
}

int main()
{
	memset(head, -1, sizeof head);
	cin>>n>>m>>ss>>tt;
	s = 0;
	t = n+1;
	for(int i = 0; i < m; i++)
	{
		int a, b, c, d; cin>>a>>b>>c>>d;
		add(a, b, c, d);
		delta[a] -= c;
		delta[b] += c;
	}
	for(int i = 1; i <= n; i++)
	{
		if(delta[i] > 0)
		{
			add(s, i, 0, delta[i]);
			tott += delta[i];
		}
		else if(delta[i] < 0)
			add(i, t, 0, -delta[i]);
	}
	add(tt, ss, 0, inf);
	if(dinic() != tott)
	{
		cout<<"NO";
		return 0;
	}
	cout<<"YES"<<endl;
	cout<<e[tot].cap-e[tot].flow<<endl;
	e[tot].flow = e[tot].cap;
	return 0;
}

有源汇上下界最大流

算法详解:先使用有源汇上下界可行流算法算出一个可行流,再在这个残余网络上跑一遍ss到tt的最大流。最终的最大流流量 = 可行流流量(即t到s的无穷边上跑出的流量) + 新增广出的ss到tt的流量。

#include<bits/stdc++.h>
using namespace std;

const int N = 2000 + 10;
const int M = (100210 + N) + 10;
const int inf = 0x3f3f3f3f;

int n, m, s, t, ss, tt, tot = 1 , tott;
int head[M<<1], d[N], cur[M<<1], delta[N];

struct Edge
{
	int to, next, cap, flow;
}e[M<<1];

void add(int a, int b, int c, int d)
{
	e[++tot].cap = d-c; e[tot].flow = 0; e[tot].to = b; e[tot].next = head[a]; head[a] = tot;
	e[++tot].cap = 0; e[tot].flow = 0; e[tot].to = a; e[tot].next = head[b]; head[b] = tot; 
}

bool bfs(int s, int t)
{
	memset(d, 0, sizeof d);
	d[s] = 1;
	cur[s] = head[s];
	queue<int> q;
	q.push(s);
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		for(int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].to;
			if(!d[v] && e[i].cap > e[i].flow)
			{
				d[v] = d[u]+1;
				cur[v] = head[v];
				if(v == t) return 1;
				q.push(v);
			}
		}
	}
	return 0;
}

int dfs(int u, int lim, int t)
{
	if(u == t) return lim;
	int res = lim;
	for(int i = cur[u]; ~i && res; i = e[i].next)
	{
		cur[u] = i;
		int v = e[i].to;
		if(d[v] == d[u]+1 && e[i].cap > e[i].flow)
		{
			int k = dfs(v, min(res, e[i].cap-e[i].flow), t);
			if(!k) d[v] = 0;
			e[i].flow += k;
			e[i^1].flow -= k; 
			res -= k;
		}	
	}
	return lim-res;
}

int dinic()
{
	int maxflow = 0;
	while(bfs(s, t)) maxflow += dfs(s, inf, t);
	return maxflow;
}

int main()
{
	memset(head, -1, sizeof head);
	cin>>n>>m>>ss>>tt;
	s = 0;
	t = n+1;
	for(int i = 0; i < m; i++)
	{
		int a, b, c, d; cin>>a>>b>>c>>d;
		add(a, b, c, d);
		delta[a] -= c;
		delta[b] += c;
	}
	for(int i = 1; i <= n; i++)
	{
		if(delta[i] > 0)
		{
			add(s, i, 0, delta[i]);
			tott += delta[i];
		}
		if(delta[i] < 0)
			add(i, t, 0, -delta[i]);
	}
	add(tt, ss, 0, inf);
	if(dinic() != tott)
	{
		cout<<"No Solution";
		return 0;
	}
	s = ss; t = tt;
	// 先加可行流流量
	int ans = e[tot].cap-e[tot].flow;
	e[tot].flow = 0; e[tot].cap = 0;
	e[tot-1].flow = 0; e[tot-1].cap = 0;
	// 再加新增广为最大流的e(s->t)的流量
	ans += dinic();
	cout<<ans<<endl;
	return 0;
}

有源汇上下界最小流

算法详解:先使用有源汇上下界可行流算法算出一个可行流,再在这个残余网络上
跑一遍tt到ss的最大流(反向边的流量增加等价于正向边的的流量减少。因此
在残余网络上找出tt到ss的流就相当于减小了ss到tt的流)。最终的最小流流量 
= 可行流流量 - tt到ss的最大流的流量。

多源汇

多源汇最大流

算法详解:新建超级源点S,向所有源点连容量为inf的边,汇点同理。
问题即转化为普通最大流问题

费用流

最小费用最大流

算法一:最小费用路算法

每次spfa找到最短路,然后沿着spfa增流。
复杂度为 O(VE^2) 
#include<bits/stdc++.h>
using namespace std;
int n,m,s,t,cnt,ans_flow,ans_cost;
int pre[50100],vis[50100],d[50100],f[50100];
vector<int> g[50100];
struct node{
    int to,flow,cap,cost;
}e[100010];
void add(int a,int b,int c,int d)
{
    e[cnt].to=b; e[cnt].cap=c; g[a].push_back(cnt); e[cnt].cost=d; cnt++;
    e[cnt].to=a; e[cnt].cap=c; e[cnt].flow=c; g[b].push_back(cnt); e[cnt].cost=-d; cnt++;
}
bool spfa()
{
    queue<int> q;
    memset(vis,0,sizeof(vis));
    memset(d,67,sizeof(d)); d[t]=INT_MAX;
    memset(f,0,sizeof(f));
    q.push(s); vis[s]=1; f[s]=1000000000; d[s]=0;
    while(!q.empty())
    {
        int u=q.front(); q.pop(); vis[u]=0;
        for(int i=0; i<g[u].size(); i++)
        {
            int v=g[u][i];
            if(e[v].cap > e[v].flow && d[e[v].to] > d[u]+e[v].cost)
            {
                d[e[v].to]=d[u]+e[v].cost;
                f[e[v].to]=min(e[v].cap-e[v].flow,f[u]);
                pre[e[v].to]=v;
                if(!vis[e[v].to])
                {
                    q.push(e[v].to); vis[e[v].to]=1;
                }
            }
        }
    }
    return d[t] != INT_MAX;
}
void mcmf()
{
    while(spfa())
    {
        int tt=t;
        while(tt != s)
        {
            int i=pre[tt];
            e[i].flow+=f[t];
            e[i^1].flow-=f[t];
            tt=e[i^1].to;
        }
        ans_flow+=f[t];
        ans_cost+=d[t]*f[t];
    }
}
int main()
{
    cin>>n>>m>>s>>t;
    for(int i=1; i<=m; i++)
    {
        int a,b,c,d;
        cin>>a>>b>>c>>d;
        add(a,b,c,d);
    }
    mcmf();
    cout<<ans_flow<<" "<<ans_cost;
    return 0;
} 

算法二:消圈算法

用途:判断是否存在更优的解,时间复杂度低于MCMF。

poj2175-消圈算法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值