图论三
复健
图论三
网络流最大流问题
平均来看算法效率强于算法,所以我这里只学习了算法
网络流是指有一个源点和一个汇点的图,然后每条边有流量限制,我们通过这个算法可以求出源点最大流量
时间复杂度为
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());
}
网络流最小割问题
对于一个网络,我们切一刀,将所有的点划分成和两个集合,称为割,其中源点在中,汇点在中
割的容量表示所有从到 的出边的容量之和
最小割就是求得一个割使得割的容量最小
最大流最小割定理:是指在一个网络流中,能够从源点到达汇点的最大流量等于如果从网络中移除就能够导致网络流中断的边的集合的最小容量和。即在任何网络中,最大流的值等于最小割的容量。
https://www.luogu.com.cn/problem/P1344
此题要求既要输出最小割的容量又要输出最小割的边的数量
最小割的容量我们用跑一下最大流即可求出
而第二问是最小容量情况下的最小边数,我们把图的边权改为,再做一遍最小割,其中是一个大于的数(因为边数最大为),然后我们把最后跑出来的最小割容量除即为最小容量,即为此条件下的最小边数
#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);
}
网络流费用流问题
费用流问题就是给定容量的同时给定单位流量的费用
总花费最小的最大流称为最小费用最大流,总花费最大的最大流称为最大费用最大流,二者合称费用流
这里采用算法,无法解决负环(因为我们用的是找增广路,这个是后我们需要先用消圈算法消去图上的负圈)
但是注意为了退流,我们的反向边流量为,为了退费,我们反向边的单位费用却应该设为
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;
}
正因无所有,才会无所畏
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现