网络流
网络是一个有向图\(G=(V,E)\),每一条边有一个流量限制\(w\),流过这条边的流量不能超过\(w\),可以理解为一个水管组成的图,每个水管的流量都是固定的,结点不能存水,只能决定从每条入边流进来多少水,每个出边流出多少水,并且流入的水等于流出的水;
网络流问题分为有源汇与无源汇两种,最大流即一种有源汇问题。
最大流
在一张有源汇网络中,求起点\(s\)到终点\(t\)的最大流量
增广
常见的最大流解法之一,另一种有机会再学。
约定
定义一条增广路为从\(s\)到\(t\)的一条路经,使得当前流到终点\(t\)的流量增加。
定义参量网络\(G*\)为\(G\)增广后所有剩余容量大于0的边与有效节点组成的网络
对每一条边\((u,v)\)建一条反边\((v,u)\),初始流量限制为0,当一条边流量\(-k\)时,让他的反边\(+k\)
为什么这样是对的呢?假设网络已经被一条增广路一次,重新从源点搜到第一条增广路上的一个结点\(u\)时,走其中一条反边\((u,v)\)就相当于撤回上一次增广中\((v,u)\)流过的部分流量换成这次流进\(u\)的流量,然后继续从\(v\)出发继续流,从而简单的支持反悔。
建反边有一种简洁的方式,通过链式反向星存图,令边的编号为\(i\)的边的反边编号为\(i\)^1,这样2,3是一对反边,4,5是一对反边……但是初始的cnt=0要换成cnt=1。
Edmonds-Karp
思想:不断通过从源点\(s\) bfs寻找长度最短的增广路,时间复杂度\(O(n*m^2)\)
每次在增广路上的边\((E_1)\)的剩余流量减去增广的流量\(k\),增广路上的反边\((E_1')\)加k。
当源点\(s\)与汇点\(t\)不连通时说明残量网络不存在增广路了,结束循环返回结果。
模板题P3376code:
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<unordered_map>
#include<vector>
#define gc getchar
#define N 205
#define M 5005
#define inf (int)(1e18)
#define pii pair<int,int>
#define nzn puts("***************")
#define r1(x,a,b) for(int x=a;x<=b;x++)
#define int long long
int rd(){
int x=0,f=1;char c=gc();
for(;!isdigit(c);c=gc())f=c==45?-1:f;
for(; isdigit(c);c=gc())x=x*10+c-'0';
return x*f;
}
using namespace std;
int n,m,s,t;
struct G_Flows{
int hd[N],cnt=1,fl[N],prv[N];
struct G_Edge{int v,nxt,w;}e[M<<1];
void add(int u,int v,int w){
e[++cnt]={v,hd[u],w},hd[u]=cnt;
e[++cnt]={u,hd[v],0},hd[v]=cnt;
}
int upd(int s,int t){
int ret=0;
while(1){
r1(i,1,n)fl[i]=-1;
fl[s]=inf;
queue <int> q;
q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=hd[u];i;i=e[i].nxt){
int v=e[i].v;
if(fl[v]==-1&&e[i].w)q.push(v),fl[v]=min(fl[u],e[i].w),prv[v]=i;
}
}
if(fl[t]==-1)return ret;
ret+=fl[t];
for(int i=prv[t];i;i=prv[ e[i^1].v ] )e[i].w-=fl[t],e[ i^1 ].w+=fl[t];
}
}
}g;
signed main(){
n=rd(),m=rd(),s=rd(),t=rd();
r1(i,1,m){
int u=rd(),v=rd(),w=rd();
g.add(u,v,w);
}
printf("%lld",g.upd(s,t));
return 0;
}
Dinic
dinic算法与EK思想差不多,都是通过不断增广求最大流,但时间复杂度为\(O(n^2m)\),实际甚至远远跑不到,在稠密图上优势更加显著。dinic的优势在于可以进行多路同时增广,算法流程:对残量网络bfs分层,再dfs多路增广,传入当前结点和流量,再流入下一个结点增广
对网络分层的意义是可以把剩下的网络看作一个dag防止dfs一直在环上跑。
dinic的一个关键优化:当前弧优化
流满的边没有dfs的必要,我们可以对每个节点\(u\)记录第一个没有流满的边\(cur[u]\),直接从这个边开始流,\(cur[u]\)初始值为\(hd[u]\)
模板题P3376code:
#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
#include<unordered_map>
#include<vector>
#define gc getchar
#define N 205
#define M 5005
#define inf (int)(1e18)
#define pii pair<int,int>
#define nzn puts("***************")
#define r1(x,a,b) for(int x=a;x<=b;x++)
#define int long long
int rd(){
int x=0,f=1;char c=gc();
for(;!isdigit(c);c=gc())f=c==45?-1:f;
for(; isdigit(c);c=gc())x=x*10+c-'0';
return x*f;
}
using namespace std;
int n,m,s,t;
struct G_Flows{
int hd[N],cnt=1,cur[N],dep[N];
struct G_Edge{int v,nxt,w;}e[M<<1];
void add(int u,int v,int w){
e[++cnt]={v,hd[u],w},hd[u]=cnt;
e[++cnt]={u,hd[v],0},hd[v]=cnt;
}
int dfs(int u,int res){
if(u==t)return res;
int ret=0;
for(int i=cur[u];i&&res;i=e[i].nxt){
cur[u]=i;
int v=e[i].v;
if(dep[v]==dep[u]+1&&e[i].w){
int k=dfs(v,min(e[i].w,res));
res-=k,e[i].w-=k,e[i^1].w+=k,ret+=k;
}
}
if(ret==0)dep[u]=-1;
return ret;
}
int upd(int s,int t){
int ret=0;
int ed=t;
while(1){
r1(i,1,n)cur[i]=hd[i],dep[i]=-1;
dep[s]=0;
queue<int>q;q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=cur[u];i;i=e[i].nxt){
int v=e[i].v;
if(dep[v]==-1&&e[i].w)q.push(v),dep[v]=dep[u]+1;
}
}
if(dep[t]==-1)return ret;
ret+=dfs(s,inf);
}
}
}g;
signed main(){
n=rd(),m=rd(),s=rd(),t=rd();
r1(i,1,m){
int u=rd(),v=rd(),w=rd();
g.add(u,v,w);
}
printf("%lld",g.upd(s,t));
return 0;
}
做题记录:
I.[P3254 圆桌问题] ( https://www.luogu.com.cn/problem/P3254 )
将一个代表看作一点流量,从源点\(S\)连m条边到结点\([1,m]\)代表\(m\)个单位,边权为\(r_i\);从\([m+1,m+n]\)分别连\(n\)条边到\(T\),再从每个单位\([1,m]\)连一条流量限制为1的边到每个桌\([m+1,m+1]\),跑一边最大流,若最大流\(\ne tot(人数)\)输出零,否则根据网络信息构造方案即可。主函数部分代码:
signed main(){
int mb=(&b)-(&a);
m=rd(),n=rd(),t=m+n+1;
r1(i,1,m){
int w=rd();tot+=w,
g.add(0,i,w);
}
r1(i,1,n){
int w=rd();
g.add(i+m,t,w);
}
r1(i,1,m)
r1(j,1,n)g.add(i,m+j,1);
if(g.Maxflow(0,t)!=tot)return puts("0"),0;
puts("1");
r1(i,1,m){
for(int j=g.hd[i];j;j=g.e[j].nxt){
if(g.e[j].w==0&&g.e[j].v>m)cout<<g.e[j].v-m<<" ";
}
cout<<"\n";
}
return 0;
}
有点难想,看到每个点都只能最多有 \(k\) 个区间覆盖,那么就要想办法构造流量限制为 \(k\) 的一些边来满足这个条件。
因为每个点最多有 \(k\) 个区间覆盖不好表示,所以转换为每个点可以有 \(k\) 个流量,然后选了一个线就在
我们把每个点 \(i\) 连一条容量无穷大的边到 \(i+1\),然后把源点 \(S\) 往 \(1\) 连一条容量为 \(k\) 的边,

浙公网安备 33010602011771号