学习笔记:网络流
学习笔记 网络流
基本概念
一个网络是一张有向无环图,每条边有容量 ,表示这条边最多能容纳多少流量,入度为0的点叫源点 ,出度为0的点叫汇点 ,源点有无限流量可以提供,最终要流向汇点,每个点流量守恒,即流入量等于流出量。
残量网络:找完增广路后并把边的容量相应更新后剩下的网络。
最大流
最大流问题是问从源点能流到汇点的最大流量是多少。
引入反向边:初始容量为0,当u给v流了w的流量,反向边容量增加w,当流量流经反向边时相当于在原边上退流,相当于提供了反悔机会。
增广路:从源点到汇点的一条流量可流过的路径。
最大流算法本质是找增广路。
EK算法
用队列增广,记录每个点的流向它的边last,增广到汇点后把边的容量更新,重复此过程直到找不到增广路。
复杂度
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl putchar('\n')
using namespace std;
const int M = 2e3+10;
const int inf = 2147483647;
typedef long long ll;
inline int read(){
int x=0,f=0;char c=getchar();
while(!isdigit(c)){
if(c=='-') f=1;c=getchar();
}
do{
x=(x<<1)+(x<<3)+(c^48);
}while(isdigit(c=getchar()));
return f?-x:x;
}
struct Edge{
int t,w,next;
};
Edge e[M*10];int head[M],tot=1;
int n,m,s,t,flow[M],last[M];
int vis[M][M];
inline void add(int f,int t,int w){
e[++tot].t=t;
e[tot].w=w;
e[tot].next=head[f];
head[f]=tot;
}
bool Find(){
memset(last,-1,sizeof(last));
flow[s]=inf;
queue<int> q;
q.push(s);
while(!q.empty()){
int x=q.front();
q.pop();
if(x==t) break;
for(int i=head[x];i;i=e[i].next){
int v=e[i].t,w=e[i].w;
if(w>0&&last[v]==-1){
last[v]=i;
flow[v]=min(flow[x],w);
q.push(v);
}
}
}
return last[t]!=-1;
}
int EK(){
int maxflow=0;
while(Find()){
maxflow+=flow[t];
for(int i=t;i!=s;i=e[last[i]^1].t){
e[last[i]].w-=flow[t];
e[last[i]^1].w+=flow[t];
}
}
return maxflow;
}
signed main(){
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
if(!vis[u][v]){
add(u,v,w);
vis[u][v]=tot;
add(v,u,0);
}
else{
e[vis[u][v]].w+=w;
}
}
printf("%lld\n",EK());
return 0;
}
Dinic算法
先BFS划出分层图,每次DFS找增广路时都往下一层增广
当前弧优化:记录每个点起始遍历的边,保证每个点起始的每条边只被遍历一次
复杂度
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl putchar('\n')
using namespace std;
const int M = 1e4+10;
const int inf = 2147483647;
typedef long long ll;
inline int read(){
int x=0,f=0;char c=getchar();
while(!isdigit(c)){
if(c=='-') f=1;c=getchar();
}
do{
x=(x<<1)+(x<<3)+(c^48);
}while(isdigit(c=getchar()));
return f?-x:x;
}
struct Edge{
int t,w,next;
};
Edge e[M<<1];int head[M],tot=1;
int n,m,s,t,lev[M],cur[M];
inline void add(int f,int t,int w){
e[++tot]=(Edge){t,w,head[f]};head[f]=tot;
e[++tot]=(Edge){f,0,head[t]};head[t]=tot;
}
bool bfs(){
memset(lev,-1,sizeof(lev));
memcpy(cur,head,sizeof(head));
lev[s]=0;
queue<int> q;
q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x];i;i=e[i].next){
int v=e[i].t,w=e[i].w;
if(w>0&&lev[v]==-1){
lev[v]=lev[x]+1;
q.push(v);
}
}
}
return lev[t]!=-1;
}
int dfs(int u,int flow){
if(flow<=0||u==t) return flow;
int rest=flow;
for(int i=cur[u];i&&rest;i=e[i].next){
cur[u]=i;
int v=e[i].t,w=e[i].w;
if(w>0&&lev[v]==lev[u]+1){
int res=dfs(v,min(rest,w));
rest-=res;
e[i].w-=res;
e[i^1].w+=res;
}
}
return flow-rest;
}
int dinic(){
int ans=0;
while(bfs()) ans+=dfs(s,inf);
return ans;
}
signed main(){
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
add(u,v,w);
}
printf("%lld\n",dinic());
return 0;
}
费用流
每条边多了一个费用,每流过1单位流量就花费
最小费用最大流:在保证是最大流的前提下使费用最小。
spfa费用流
类似EK算法,但是把增广部分改成了spfa,每次增广更新最短路,每个流量为flow的增广路会新产生 的费用
点击查看代码
#include <bits/stdc++.h>
#define endl putchar('\n')
using namespace std;
const int M = 5e3+10;
const int inf = 2147483647;
inline int read(){
int x=0,f=0;char c=getchar();
while(!isdigit(c)){
if(c=='-') f=1;c=getchar();
}
do{
x=(x<<1)+(x<<3)+(c^48);
}while(isdigit(c=getchar()));
return f?-x:x;
}
struct Edge{
int t,w,c,next;
};
Edge e[M*40];int head[M],tot=1;
int n,m,s,t,ans1,ans2,dis[M],flow[M],last[M],inq[M];
queue<int> q;
inline void add(int f,int t,int w,int c){
e[++tot]=(Edge){t,w,c,head[f]};head[f]=tot;
e[++tot]=(Edge){f,0,-c,head[t]};head[t]=tot;
}
bool spfa(){
memset(dis,0x3f,sizeof(dis));
memset(inq,0,sizeof(inq));
memset(last,-1,sizeof(last));
dis[s]=0;
flow[s]=inf;
q.push(s);
while(!q.empty()){
int x=q.front();q.pop();
inq[x]=0;
for(int i=head[x];i;i=e[i].next){
int v=e[i].t,w=e[i].w,c=e[i].c;
if(w>0&&dis[v]>dis[x]+c){
dis[v]=dis[x]+c;
last[v]=i;
flow[v]=min(flow[x],w);
if(!inq[v]){
inq[v]=1;
q.push(v);
}
}
}
}
return last[t]!=-1;
}
void EK(){
while(spfa()){
ans1+=flow[t];
ans2+=flow[t]*dis[t];
for(int i=t;i!=s;i=e[last[i]^1].t){
e[last[i]].w-=flow[t];
e[last[i]^1].w+=flow[t];
}
}
}
int main(){
n=read();m=read();s=read();t=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read(),c=read();
add(u,v,w,c);
}
EK();
printf("%d %d\n",ans1,ans2);
return 0;
}
上下界网络流
每条边有了最低流量限制 和最高流量限制
无源汇上下界可行流
先假设所有边的流量都是他们的流量下界 ,上界相应变成 ,此时不一定满足流量守恒,对于一个点 ,设它的流入量为 ,流出量为 ,建立虚拟源点 和虚拟汇点 。若 ,也就是要附加流要流进来更多,则要给多的流量找去路,连一条从 到 容量为 的边,同理若 ,则连一条 到 的容量为 的边
若 连出去的边可以满流则说明存在可行流
此时每条边流量为每条边的反向边容量加上它的下界
有源汇上下界可行流
连一条从 到 下界为0上界为 的边即可
有源汇上下界最大流
先求一遍可行流 ,删去 到 的边后在残量网络上跑从 到 的最大流 ,答案即为
有源汇上下界最小流
同上,但最后改成跑从 到 的最大流,答案为
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效