-
杂谈
初步涉猎网络流,谈谈自己的理解,蒟蒻会不定期补充的 qwq
-
网络流问题
-
最大流问题:
先举一个运输方案的例子 :
可行方案:
总运输量5百吨。 显而易见一个可行方案有如下性质:
而现在的问题转变为求Vs到Vt的最大运输量
概念概述:
-
EK算法
感性理解则为:增广的流量一定要符合短板效应,即等于路径上残量的最小值。
EK算法具体实现:标程
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdlib>
using namespace std;
const int maxn=105;
const int maxm=1005;
const int inf=0x7fffffff;
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,m,s,t;
struct edge
{
int u,to,c,next;
}g[maxm<<1];
int head[maxn],cnt(0);
inline void add(int a,int b,int c)
{
g[cnt].u=a; g[cnt].to=b;
g[cnt].c=c;
g[cnt].next=head[a];
head[a]=cnt++;
return ;
}
int pre[maxn];
void bfs(int s,int t)
{
memset(pre,-1,sizeof(pre));
queue<int> q;
q.push(s);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i!=-1;i=g[i].next)
{
int v=g[i].to;
if(pre[v]!=-1||!g[i].c) continue;
pre[v]=i;
if(v==t) break;
q.push(v);
}
if(pre[t]!=-1) break;
}
return ;
}
int ans(0);
int EK(int s,int t)
{
ans=0;
while(1)
{
bfs(s,t);
if(pre[t]==-1) break;
int mn=inf;
for(int i=t;i^s;i=g[pre[i]].u)
mn=min(mn,g[pre[i]].c);
for(int i=t;i^s;i=g[pre[i]].u)
{
g[pre[i]].c-=mn;
g[pre[i]^1].c+=mn;
}
ans+=mn;
}
return ans;
}
int main()
{
n=read(); m=read();
s=read(); t=read();
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++)
{
int a,b,v;
a=read(); b=read();
v=read();
add(a,b,v); add(b,a,0);
}
printf("%d\n",EK(s,t));
return 0;
}
/*4 4 1 4
1 2 5
1 3 1
2 4 2
3 4 3*/
话说网络流真令人不爽,head存图时候要初始-1,链式前向星存边编号从0开始
-
Dinic算法
dinic算法同EK一样,都是不断去找增广路的过程
板子如下:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdlib>
using namespace std;
const int maxn=1205;
const int maxm=1.2e5+5;
const int inf=0x7fffffff;
#define int long long
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
struct edge
{
int u,to,next,c;
}g[maxm<<1];
int head[maxn],cnt(0);
int n,m,s,t;
inline void add(int a,int b,int c)
{
g[cnt].to=b; g[cnt].u=a;
g[cnt].c=c;
g[cnt].next=head[a];
head[a]=cnt++;
return ;
}
int dep[maxn];
bool bfs(int s,int t)
{
memset(dep,-1,sizeof(dep));
queue<int> q;
q.push(s); dep[s]=0;
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i!=-1;i=g[i].next)
{
int v=g[i].to;
if(dep[v]^-1||!g[i].c) continue;
dep[v]=dep[u]+1;
q.push(v);
}
//构建网络,进行分层
}
return (dep[t]!=-1);
}
int dfs(int u,int flow)
{
if(u==t) return flow;
//到达汇点flow可以作为最终流量
int res=0;
for(int i=head[u];i!=-1;i=g[i].next)
{
int v=g[i].to;
if(dep[v]==dep[u]+1&&g[i].c)
{
int x=dfs(v,min(flow,g[i].c));
flow-=x; res+=x;
g[i].c-=x; g[i^1].c+=x;
if(!flow) break;
//余额用尽,不能增广了
}
}
if(!res) dep[u]=-1; //炸点,即从这个点出发不可能增广
return res; //返回的是该点增广的总流量
}
int dinic()
{
int ans(0);
while(bfs(s,t))
ans+=dfs(s,inf);
return ans;
}
signed main()
{
n=read(); m=read();
s=read(); t=read();
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++)
{
int u,v,c;
u=read(); v=read();
c=read();
add(u,v,c); add(v,u,0);
}
printf("%lld\n",dinic());
return 0;
}
-
最大流最小割
首先介绍什么是割,但一般指s-t割
-
最小费用最大流
这个问题有一个明确的方法,因为不论选择哪条增广路优先增广都能求出最大流,因此我们确保每次求增广路的bfs用spfa替代,就能保证一定求出最小费用的最大流,若求最大费用,就spfa求最长路。
关于求法,我们只需将bfs替换后接上ek或者dinic即可
P3381 【模板】最小费用最大流https://www.luogu.com.cn/problem/P3381 板子题,上板子:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdlib>
using namespace std;
const int maxn=3e5+5;
const int maxm=5e4+5;
const int inf=0x3f3f3f3f;
typedef pair<int,int> pii;
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,m,s,t;
struct edge
{
int u,to,c;
int w,next;
}g[maxm<<1];
int head[maxn],cnt(0);
void add(int a,int b,int c,int w)
{
g[cnt].u=a,g[cnt].to=b;
g[cnt].c=c; g[cnt].w=w;
g[cnt].next=head[a];
head[a]=cnt++;
return ;
}
int dis[maxn];
bool vis[maxn];
int pre[maxn];
bool spfa(int s)
{
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(pre,-1,sizeof(pre));
queue<int> q;
q.push(s);
dis[s]=0; vis[s]=1;
while(!q.empty())
{
int u=q.front();
q.pop(); vis[u]=0;
for(int i=head[u];i!=-1;i=g[i].next)
{
if(!g[i].c) continue;
//这里只需要确保是增广路即可,不用验pre[v]是否访问过
int v=g[i].to;
if(dis[v]>dis[u]+g[i].w)
{
dis[v]=dis[u]+g[i].w;
pre[v]=i;
if(!vis[v]) q.push(v),vis[v]=1;
}
}
}
return (pre[t]!=-1);
}
pii EK(int s,int t)
{
int ans(0),res(0);
pii kt;
while(spfa(s))
{
int mn=inf;
for(int i=t;i^s;i=g[pre[i]].u)
mn=min(mn,g[pre[i]].c);
for(int i=t;i^s;i=g[pre[i]].u)
{
g[pre[i]].c-=mn;
g[pre[i]^1].c+=mn;
ans+=mn*g[pre[i]].w;
}
res+=mn;
//在这里改ans也行
//ans+=mn*dis[t];
}
kt.first=res,kt.second=ans;
return kt;
}
int main()
{
n=read(); m=read();
s=read(); t=read();
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++)
{
int u,v,c,w;
u=read(); v=read();
c=read(); w=read();
add(u,v,c,w);
add(v,u,0,-w);
}
pii tmp=EK(s,t);
printf("%d %d\n",tmp.first,tmp.second);
return 0;
}
-
二分图&匹配
有点抽象,翻译成人话,如下图所示,极大匹配,意思是边数最多的匹配,完美匹配,意思是把所有点都用上的匹配,饱和点就是下图u,v里面的点的集合
-
匈牙利算法
不断增广的过程,由于一次增广每个点只需要访问一次
代码如下:
upd:2023-3-26
二分图最大匹配的代码有问题
下面才是正确的
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<queue>
#include<vector>
#include<cstdlib>
using namespace std;
const int maxn=1e3+5;
const int maxm=1e6+5;
inline int read()
{
int x=0,y=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') y=-1; c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,m,e;
struct egde
{
int to,next;
}g[maxm<<1];
int head[maxn],cnt(0);
inline void add(int a,int b)
{
g[++cnt].to=b;
g[cnt].next=head[a];
head[a]=cnt;
return ;
}
int rmatch[maxn];
bool vis[maxn];
bool dfs(int u)
{
vis[u]=1;
for(int i=head[u];i;i=g[i].next)
{
int v=g[i].to;
if(vis[rmatch[v]]) continue;
if(!rmatch[v]||dfs(rmatch[v]))
{rmatch[v]=u; return 1;}
}
return false;
}
int km()
{
int res=0;
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
res+=(dfs(i)==1);
}
return res;
}
int main()
{
n=read(); m=read(); e=read();
for(int i=1;i<=e;i++)
{
int u,v;
u=read(); v=read();
if(u<=n&&v<=m) add(u,v);
}
printf("%d\n",km());
return 0;
}