学习笔记:网络流
学习笔记 网络流
基本概念
一个网络是一张有向无环图,每条边有容量 \(w\),表示这条边最多能容纳多少流量,入度为0的点叫源点 \(s\),出度为0的点叫汇点 \(t\),源点有无限流量可以提供,最终要流向汇点,每个点流量守恒,即流入量等于流出量。
残量网络:找完增广路后并把边的容量相应更新后剩下的网络。
最大流
最大流问题是问从源点能流到汇点的最大流量是多少。
引入反向边:初始容量为0,当u给v流了w的流量,反向边容量增加w,当流量流经反向边时相当于在原边上退流,相当于提供了反悔机会。
增广路:从源点到汇点的一条流量可流过的路径。
最大流算法本质是找增广路。
EK算法
用队列增广,记录每个点的流向它的边last,增广到汇点后把边的容量更新,重复此过程直到找不到增广路。
复杂度\(O(nm^2)\)
点击查看代码
#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找增广路时都往下一层增广
当前弧优化:记录每个点起始遍历的边,保证每个点起始的每条边只被遍历一次
复杂度\(O(n^2m)\)
点击查看代码
#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;
}
费用流
每条边多了一个费用\(c\),每流过1单位流量就花费\(c\)
最小费用最大流:在保证是最大流的前提下使费用最小。
spfa费用流
类似EK算法,但是把增广部分改成了spfa,每次增广更新最短路,每个流量为flow的增广路会新产生 \(flow*dis[t]\) 的费用
点击查看代码
#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;
}
上下界网络流
每条边有了最低流量限制 \(L\) 和最高流量限制 \(R\)
无源汇上下界可行流
先假设所有边的流量都是他们的流量下界 \(L\),上界相应变成 \(R-L\),此时不一定满足流量守恒,对于一个点 \(i\),设它的流入量为 \(x\),流出量为 \(y\),建立虚拟源点 \(ss\) 和虚拟汇点 \(tt\)。若 \(y>x\),也就是要附加流要流进来更多,则要给多的流量找去路,连一条从 \(i\) 到 \(tt\) 容量为 \(x-y\) 的边,同理若 \(x>y\),则连一条 \(ss\) 到 \(i\) 的容量为 \(y-x\) 的边
若 \(s\) 连出去的边可以满流则说明存在可行流
此时每条边流量为每条边的反向边容量加上它的下界
有源汇上下界可行流
连一条从 \(t\) 到 \(s\) 下界为0上界为 \(inf\) 的边即可
有源汇上下界最大流
先求一遍可行流 \(flow1\),删去 \(t\) 到 \(s\) 的边后在残量网络上跑从\(s\) 到 \(t\) 的最大流 \(flow2\),答案即为 \(flow1+flow2\)
有源汇上下界最小流
同上,但最后改成跑从 \(t\) 到 \(s\) 的最大流,答案为 \(flow1-flow2\)