图论三

复健Day7

图论三

1.网络流最大流问题

平均来看Dinic算法效率强于EK算法,所以我这里只学习了Dinic算法

网络流是指有一个源点和一个汇点的图,然后每条边有流量限制,我们通过这个算法可以求出源点最大流量

时间复杂度为O(nm2)

https://www.luogu.com.cn/problem/P3376

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
using namespace std;

const int maxn=1e4+10;;

int head[maxn],tot;
int d[maxn],cur[maxn];//d数组存u点所在的图层,cur数组存u点当前的出边
int s,t;

struct Edge
{
	int v,w,nxt;
	Edge(){}
	Edge(int v,int w,int nxt):v(v),w(w),nxt(nxt){}
}ed[maxn];

void add(int u,int v,int w)
{
	ed[tot]=Edge(v,w,head[u]);
	head[u]=tot++;
}


bool bfs()//对点分层,并找有没有增广路
{
	memset(d,0,sizeof(d));
	queue<int> q;
	q.push(s);
	d[s]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			int v=ed[i].v;
			if(d[v]==0&&ed[i].w)//如果v还没有分层并且还有剩余容量
			{
				d[v]=d[u]+1;
				q.push(v);
				if(v==t) return true;
			}
		}
	}
	return false;//找不到增广路
}

int dfs(int u,int mf)//多路增广 
{
	if(u==t) return mf;//mf指走到u点是的剩余容量
	int sum=0;
	for(int i=cur[u];~i;i=ed[i].nxt)
	{
		cur[u]=i;
		int v=ed[i].v;
		if(d[v]==d[u]+1&&ed[i].w)
		{
			int f=dfs(v,min(mf,ed[i].w));
			ed[i].w-=f;
			ed[i^1].w+=f;
			sum+=f;//累加u的流出流量
			mf-=f;//剩余流量
			if(mf==0) break;
		}
	}
	if(sum==0) d[u]=0;//残枝优化,即流不到汇点了
	return sum;
}

int dinic()//累计可行流
{
	int flow=0;
	while(bfs())
	{
		memcpy(cur,head,sizeof(head));
		flow+=dfs(s,1e9);
	}
	return flow;
}

signed main()
{
	memset(head,-1,sizeof(head));
	int n,m;
	cin>>n>>m>>s>>t;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w);
		add(v,u,0);
	}
	printf("%lld\n",dinic());
}

2.网络流最小割问题

对于一个网络,我们切一刀,将所有的点划分成st两个集合,称为割(s,t),其中源点在s中,汇点在t

割的容量c表示所有从st的出边的容量之和

最小割就是求得一个割使得割的容量最小

最大流最小割定理:是指在一个网络流中,能够从源点到达汇点的最大流量等于如果从网络中移除就能够导致网络流中断的边的集合的最小容量和。即在任何网络中,最大流的值等于最小割的容量

https://www.luogu.com.cn/problem/P1344

此题要求既要输出最小割的容量又要输出最小割的边的数量

最小割的容量我们用dinic跑一下最大流即可求出

而第二问是最小容量情况下的最小边数,我们把图的边权改为a×f+1,再做一遍最小割,其中a是一个大于1000的数(因为边数最大为1000),然后我们把最后跑出来的最小割容量除a即为最小容量,moda即为此条件下的最小边数

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
using namespace std;

const int maxn=1e4+10;;

int head[maxn],tot;
int d[maxn],cur[maxn];//d数组存u点所在的图层,cur数组存u点当前的出边
int s,t;

struct Edge
{
	int v,w,nxt;
	Edge(){}
	Edge(int v,int w,int nxt):v(v),w(w),nxt(nxt){}
}ed[maxn];

void add(int u,int v,int w)
{
	ed[tot]=Edge(v,w,head[u]);
	head[u]=tot++;
}

bool bfs()//对点分层,并找有没有增广路
{
	memset(d,0,sizeof(d));
	queue<int> q;
	q.push(s);
	d[s]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			int v=ed[i].v;
			if(d[v]==0&&ed[i].w)//如果v还没有分层并且还有剩余容量
			{
				d[v]=d[u]+1;
				q.push(v);
				if(v==t) return true;
			}
		}
	}
	return false;//找不到增广路
}

int dfs(int u,int mf)//多路增广 
{
	if(u==t) return mf;//mf指走到u点是的剩余容量
	int sum=0;
	for(int i=cur[u];~i;i=ed[i].nxt)
	{
		cur[u]=i;
		int v=ed[i].v;
		if(d[v]==d[u]+1&&ed[i].w)
		{
			int f=dfs(v,min(mf,ed[i].w));
			ed[i].w-=f;
			ed[i^1].w+=f;
			sum+=f;//累加u的流出流量
			mf-=f;//剩余流量
			if(mf==0) break;
		}
	}
	if(sum==0) d[u]=0;//残枝优化,即流不到汇点了
	return sum;
}

int dinic()//累计可行流
{
	int flow=0;
	while(bfs())
	{
		memcpy(cur,head,sizeof(head));
		flow+=dfs(s,1e9);
	}
	return flow;
}

signed main()
{
	memset(head,-1,sizeof(head));
	int n,m;
	cin>>n>>m;
	s=1,t=n;
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		cin>>u>>v>>w;
		add(u,v,w*1001+1);
		add(v,u,0);
	}
	int ans=dinic();
	printf("%lld %lld\n",ans/1001,ans%1001);
}

3.网络流费用流问题

费用流问题就是给定容量的同时给定单位流量的费用

总花费最小的最大流称为最小费用最大流,总花费最大的最大流称为最大费用最大流,二者合称费用流

这里采用EK算法,无法解决负环(因为我们用的是spfa找增广路,这个是后我们需要先用消圈算法消去图上的负圈)

但是注意为了退流,我们的反向边流量为0,为了退费,我们反向边的单位费用却应该设为d

https://www.luogu.com.cn/problem/P3381

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;

const int maxn=1e5+10;
const int inf=1e8;
int head[maxn],tot;
int d[maxn],mf[maxn],pre[maxn],vis[maxn];
int flow,cost;
int n,m,s,t;

struct Edge
{
	int v,c,w,nxt;
	Edge(){}
	Edge(int v,int c,int w,int nxt):v(v),c(c),w(w),nxt(nxt){}
}ed[maxn<<1];

void add(int u,int v,int c,int w)
{
	ed[tot]=Edge(v,c,w,head[u]);
	head[u]=tot++;
}

bool spfa()
{
	memset(d,0x3f,sizeof(d));
	memset(mf,0,sizeof(mf));
	queue<int> q;
	q.push(s);
	d[s]=0,mf[s]=inf,vis[s]=1;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=head[u];~i;i=ed[i].nxt)
		{
			int v=ed[i].v;
			int c=ed[i].c,w=ed[i].w;
			if(d[v]>d[u]+w&&c)
			{
				d[v]=d[u]+w;
				mf[v]=min(mf[u],c);
				pre[v]=i;//前驱边
				if(!vis[v])
				{
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
	return mf[t]>0;
}

void EK()
{
	while(spfa())
	{
		for(int v=t;v!=s;)
		{
			int i=pre[v];
			ed[i].c-=mf[t];
			ed[i^1].c+=mf[t];
			v=ed[i^1].v;
		}
		flow+=mf[t];
		cost+=mf[t]*d[t];
	}
}

int read()
{
	int ans=0,f=1;char i=getchar();
	while(i<'0'||i>'9'){if(i=='-') f=-1;i=getchar();}
	while(i>='0'&&i<='9'){ans=ans*10+i-'0';i=getchar();}
	return ans*f;
}

int main()
{
	memset(head,-1,sizeof(head));
	n=read();m=read();s=read();t=read();
	int u,v,c,w;
	while(m--)
	{
		u=read();v=read();c=read();w=read();
		add(u,v,c,w);
		add(v,u,0,-w);
	}
	EK();
	printf("%d %d\n",flow,cost);
	return 0;
}

posted on   dolires  阅读(6)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示