网络流总结

这里全是板子。。。

先来一个模板题压压惊

​​​​​​模板题

双倍经验

先来一个EK算法(有些解释放在注释里了)

利用bfs实现,时间复杂度O(V*E^2)≈ FF;

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
	ll t,l,next;
}edge[N<<1];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x;
}
void add(ll a,ll b,ll c)
{
	edge[++cnt].t=b;
	edge[cnt].l=c;
	edge[cnt].next=head[a];
	head[a]=cnt;
}
ll inque[N];
struct Pre
{
    ll v;//前一个节点
	ll edge;//前一条边	
}pre[N];


ll n,m,s,t;
ll a,b,c; 

bool bfs()//点到终点是否还有路可以走
{
	queue<ll> q;
	memset(inque,0,sizeof inque);
	memset(pre,-1,sizeof pre); 
	inque[s]=1;//标记起点
	q.push(s);
	while(!q.empty())
	{
		ll ty=q.front();
		q.pop();
		for(int i=head[ty];i!=-1;i=edge[i].next)
		{
			ll y=edge[i].t;
			if(edge[i].l==0) continue;
			if(inque[y]++) continue;//如果要这么写的话,这句话要放在上一句下面
			//因为只有边大于0才有资格被加入 
			pre[y].v=ty;
			pre[y].edge=i;
			if(y==t) return 1;
			//inque[y]++;//只有 
			q.push(y);
		}
	}
	return 0; 
}


ll ek()
{
    ll ans=0;
	while(bfs())//起点到终点还有路可以走
	{
	    ll mi=inf;
		for(int i=t;i!=s;i=pre[i].v)//从终点回溯 
		{
			mi=min(mi,edge[pre[i].edge].l);//找最小边 	
		}
		for(int i=t;i!=s;i=pre[i].v)
		{
		    edge[pre[i].edge].l-=mi;
		    edge[pre[i].edge^1].l+=mi;
		}
		ans+=mi;	
	}	
	return ans;
} 
int main()
{
	
	memset(head,-1,sizeof head);
	n=read();m=read();//s=read();t=read();
	s=1;t=m; 
	for(int i=1;i<=n;++i)
	{
		a=read();b=read();c=read();
		add(a,b,c);
		add(b,a,0);//添加反向边 
	}
	cout<<ek()<<endl;
	return 0;
}

Dinic:

相比于上面算法的O(nm^2)(n为点数,m为边数),Dinic可以达到O(n^2m),在稠密图上(比如二分匹配之类的)Dinic的优势就非常明显了。

(Dinic在跑二分图匹配时比匈牙利快很多。)

下面这个为加了当前弧优化的版本

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
	ll t,l,next;
}edge[N<<1];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x;
}
void add(ll a,ll b,ll c)
{
	edge[++cnt].t=b;
	edge[cnt].l=c;
	edge[cnt].next=head[a];
	head[a]=cnt;
}
ll inque[N];
ll dep[N];//用于分层
ll cur[N];//记录弧 
ll vis;//这次是否能到达终点
ll ans=0;
struct Pre
{
    ll v;//前一个节点
	ll edge;//前一条边	
}pre[N];


ll n,m,s,t;
ll a,b,c;  

bool bfs()//用于分层
{
//    memset(inque,0,sizeof inque);
//    memset(dep,0x3f,sizeof dep);
    for(int i=0;i<=n;++i)
    {
    	cur[i]=head[i];
    	dep[i]=0x3f3f3f3f3f3f3f3f;
    	inque[i]=0;
	}
    dep[s]=0;//起点
	queue<ll> q;
	q.push(s);
	while(!q.empty())
	{
		ll ty=q.front();
		q.pop();
		inque[ty]=0;//
		for(int i=head[ty];i!=-1;i=edge[i].next)
		{
			ll y=edge[i].t;
			if(dep[y]<=dep[ty]+1) continue;//无需更新
			if(edge[i].l==0) continue;
			dep[y]=dep[ty]+1;
			if(inque[y]) continue;//看是否在队里 
			inque[y]++;
			q.push(y); 
		} 
	}
	if(dep[t]!=0x3f3f3f3f3f3f3f3f) return 1;
	return 0;	
} 

ll dfs(ll u,ll flow)//节点,当前最小流量 
{
	ll rl=0;
	if(u==t)
	{
		vis=1;
		ans+=flow;
		return flow;
	} 
	ll used=0;//该点用过的流量 
	for(int i=cur[u]/*是cur!这里不是head(当前弧优化)*/;i!=-1;i=edge[i].next)
	{
		cur[u]=i;//修改当前弧 
		ll y=edge[i].t;
		if(dep[y]==dep[u]+1&&edge[i].l)
		{
			rl=dfs(y,min(flow-used,edge[i].l));//减去后的费用拿去比较 
			if(rl==0) continue;
			used+=rl; 
			edge[i].l-=rl;
			edge[i^1].l+=rl;
			if(used==flow) break;//满了 
			//return rl;
		}
	}
	return used; 
}

ll dic()
{
	while(bfs())
	{
		vis=1;
		while(vis)
		{
		    vis=0;
			dfs(s,inf);	
		} 
	}
	return ans;
}
int main()
{
	
	memset(head,-1,sizeof head);
	n=read();m=read();s=read();t=read();
	for(int i=1;i<=m;++i)
	{
		a=read();b=read();c=read();
		add(a,b,c);
		add(b,a,0);//添加反向边 
	}
	cout<<dic()<<endl;
	return 0;
}

同样是你谷那道板子题 

可以看看两者的差距

(EK还吸了氧)

 还有重量级的ISAP

通过统计每一层的节点数以及断层处理,做到只用跑一次bfs

时间复杂度O(n^2m);

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
	ll t,l,next;
}edge[N<<1];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x;
}
void add(ll a,ll b,ll c)
{
	edge[++cnt].t=b;
	edge[cnt].l=c;
	edge[cnt].next=head[a];
	head[a]=cnt;
}
ll inque[N];
ll dep[N];//用于分层
ll gap[N];//该层深度的点有多少个 
ll cur[N];//记录弧 
ll vis;//这次是否能到达终点
ll ans=0;

ll n,m,s,t;
ll a,b,c;  

void bfs()//倒着跑一遍,标记深度 
{
	memset(dep,-1,sizeof dep);
	memset(gap,0,sizeof gap);
	dep[t]=0;//从终点往起点推
	gap[0]=1;
	queue<ll> q;
	q.push(t);
	while(!q.empty())
	{
		ll ty=q.front();
		q.pop();
		for(int i=head[ty];i!=-1;i=edge[i].next)
		{
			ll y=edge[i].t;
			if(dep[y]!=-1) continue;//代表它被访问过了
			q.push(y);
			dep[y]=dep[ty]+1;
			gap[dep[y]]++; 
		}
	}
} 

ll dfs(ll u,ll flow)
{
    if(u==t)
	{
	    ans+=flow;
		return flow;	
	}	
	ll used=0;
	for(int i=head[u];i!=-1;i=edge[i].next)
	{
		ll y=edge[i].t;
		if(edge[i].l&&dep[y]+1==dep[u])//这里顺序是反的// 
		{
			ll fl=dfs(y,min(edge[i].l,flow-used));
			if(fl)
			{
			   edge[i].l-=fl;
			   edge[i^1].l+=fl;
			   used+=fl;
		    }
			if(used==flow) return used;//前面送来的都用完了 
		}
	}
	//否则:
	gap[dep[u]]--;
	if(!gap[dep[u]]) dep[s]=n+1;
	dep[u]++;
	gap[dep[u]]++;
	return used;
} 

ll isap()
{
	ans=0;
	bfs();
	while(dep[s]<n) dfs(s,inf);
	return ans;
}
int main()
{
	
	memset(head,-1,sizeof head);
	n=read();m=read();s=read();t=read();
	for(int i=1;i<=m;++i)
	{
		a=read();b=read();c=read();
		add(a,b,c);
		add(b,a,0);//添加反向边 
	}
	cout<<isap()<<endl;
	return 0;
}

以及加上弧优化后的版本:

就是加了个cur数组来标记弧

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=101000;
#define endl '\n'
const ll inf=1<<30;
struct ty{
	ll t,l,next;
}edge[N<<1];
ll cnt=1;//后面要让第i条边^1后能得到它的反向边(与它相邻)
ll head[N];
inline ll read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x;
}
void add(ll a,ll b,ll c)
{
	edge[++cnt].t=b;
	edge[cnt].l=c;
	edge[cnt].next=head[a];
	head[a]=cnt;
}
ll inque[N];
ll dep[N];//用于分层
ll gap[N];//该层深度的点有多少个 
ll cur[N];//记录弧 
ll vis;//这次是否能到达终点
ll ans=0;

ll n,m,s,t;
ll a,b,c;  

void bfs()//倒着跑一遍,标记深度 
{
	memset(dep,-1,sizeof dep);
	memset(gap,0,sizeof gap);
	dep[t]=0;//从终点往起点推
	gap[0]=1;
	queue<ll> q;
	q.push(t);
	while(!q.empty())
	{
		ll ty=q.front();
		q.pop();
		for(int i=head[ty];i!=-1;i=edge[i].next)
		{
			ll y=edge[i].t;
			if(dep[y]!=-1) continue;//代表它被访问过了
			q.push(y);
			dep[y]=dep[ty]+1;
			gap[dep[y]]++; 
		}
	}
} 

ll dfs(ll u,ll flow)
{
    if(u==t)
	{
	    ans+=flow;
		return flow;	
	}	
	ll used=0;
	for(int i=cur[u];i!=-1;i=edge[i].next)
	{
		cur[u]=i;
		ll y=edge[i].t;
		if(edge[i].l&&dep[y]+1==dep[u])//这里顺序是反的// 
		{
			ll fl=dfs(y,min(edge[i].l,flow-used));
			if(fl)
			{
			   edge[i].l-=fl;
			   edge[i^1].l+=fl;
			   used+=fl;
		    }
			if(used==flow) return used;//前面送来的都用完了 
		}
	}
	//否则:
	gap[dep[u]]--;
	if(!gap[dep[u]]) dep[s]=n+1;
	dep[u]++;
	gap[dep[u]]++;
	return used;
} 

ll isap()
{
	ans=0;
	bfs();
	while(dep[s]<n)
	{
		memcpy(cur,head,sizeof head);
		dfs(s,inf);
	}
	return ans;
}
int main()
{
	
	memset(head,-1,sizeof head);
	n=read();m=read();s=read();t=read();
	for(int i=1;i<=m;++i)
	{
		a=read();b=read();c=read();
		add(a,b,c);
		add(b,a,0);//添加反向边 
	}
	cout<<isap()<<endl;
	return 0;
}

至于HLPP。。。以后再说吧(可能都不一定会去学) 

板子先总结到这,后面费用流也再说

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值