神仙网络最大流
好久没有写博客的lz的突然一篇博客,马上要开学了,怕是上机时间会少很多。
(本篇引用多次百度百科因为实在不知道怎么用语言描述)
有关网络流-最大流:
首先啥是网络流-最大流:
网络流,是基于有向图的;
首先有一个源点,一个汇点:
然后在源点和汇点的基础上,我们衍生出许多路径。
每条边都有一个最大流量,对于一条路径来说,这条路径的最大流量就是这条路径上最小的最大流量的值。对于分支情况,我们也可以将当前水流分别流向几个地方;
emmm挺乱的,我们看个例子?
反正大概就这个亚子。
然后我们可以把源点看做是有无限多的流量,求最大流就是求从源点最大可以流到汇点的流量是多少,上图的最大流就是9;
然后显然要考虑怎么求最大流。
引入增广路的概念:
增广路是指从S到T的一条路,流过这条路,使得当前的流量可以增加。(摘自)
EK算法:
就是不断地寻找增广路,将流量加入汇点,直至找不到增广路,此时汇点的流量就是最大流。
算法流程:
从S到T广搜,从S开始不断向外广搜,通过权值大于 0的边(因为后面会减边权值,所以可能存在边权为0的边),直到找到T为止,然后找到该路径上边权最小的边,记为\(minf\),然后最大流加\(minf\) ,然后把该路径上的每一条边的边权减去\(minf\),把该路径上的每一条边的反向边的边权加上 \(minf\) ,直到找不到一条增广路为止。
时间复杂度\(O(nm^2)\),可以处理\(10^3\)~\(10^4\)规模的网络。
然后是模板题:
#include<bits/stdc++.h>
#define inf 1000000007//然后因为把define放在这里被water_lift嫌弃毒瘤rwr
using namespace std;
inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
bitset<10010> vis;
int n,m,s,t;
int ecnt=1,ans;
int head[10010];
struct node{
int to,dis,nxt;
}e[200010];
void add(int from,int to,int dis){
++ecnt;
e[ecnt].to=to;
e[ecnt].dis=dis;
e[ecnt].nxt=head[from];
head[from]=ecnt;
}
int now[10010],pre[10010];
bool bfs(){
vis.reset();
vis[s]=1;
queue<int> q;
q.push(s);
now[s]=inf;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u],v,w;i;i=e[i].nxt){
v=e[i].to;w=e[i].dis;
if(!w||vis[v]) continue;
now[v]=min(now[u],w);
pre[v]=i;
if(v==t) return 1;
q.push(v);
vis[v]=1;
}
}
return 0;
}
void update(){
ans+=now[t];
int x=t;
while(x!=s){
int i=pre[x];
e[i].dis-=now[t];
e[i^1].dis+=now[t];
x=e[i^1].to;
}
}
int main(){
n=read();m=read();
s=read();t=read();
int u,v,w;
for(int i=1;i<=m;i++){
u=read();
v=read();
w=read();
add(u,v,w);
add(v,u,0);
}
while(bfs())update();
printf("%d",ans);
}
然后基本上长得一模一样的例题
Luogu P2740 [USACO4.2]草地排水Drainage Ditches
Dinic算法
(我是懵的rwr)
- 残量网络
在任意时刻,网络中所有节点以及剩余容量大于0的边构成的子图被称为残量网络。EK算法每轮可能会遍历整个残量网络,但只找出1条增广路。
- 分层图
节点层次 \(d_x\)表示 S 到 x 最少需要经过的边数。在残量网络中,满足 \(d_y = d_x + 1\)的边 (x,y) 构成的子图被称为分层图。显然,分层图是一张有向无环图。
- 算法步骤
不断重复以下步骤,直到残量网络中S不能到达T:
- 在残量网络上 BFS 求出节点层次,构造分层图。
- 在分层图上 DFS 寻找增广路,在回溯时实时更新剩余容量。另外,每个点可以流向多条出边,同时还加入若干剪枝。
时间复杂度
\(O(n^2m)\),实际运用中远远达不到,能够处理 \(10^4\) ~ \(10^5\)规模的网络。
上面模板题的dinic做法:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
const int mxn=10010;
const int mxm=100010;
const int inf=2147483647;
int n,m,s,t;
struct node{
int to,dis,nxt;
}e[mxm<<1];
int ecnt=1,head[mxn],dep[mxn],cur[mxn];
void add(int from,int to,int dis){
ecnt++;
e[ecnt].to=to;
e[ecnt].dis=dis;
e[ecnt].nxt=head[from];
head[from]=ecnt;
}
bool bfs(){
queue<int> q;
while(!q.empty()) q.pop();
memset(dep,0,sizeof(dep));
q.push(s);
dep[s]=1;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u],v;i;i=e[i].nxt){
v=e[i].to;
if(e[i].dis>0&&!dep[v]){
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t]==0) return 0;
return 1;
}
int dfs(int u,int dis){
if(u==t) return dis;
for(int &i=cur[u];i;i=e[i].nxt){
int v=e[i].to;
if(dep[v]==dep[u]+1&&e[i].dis!=0){
int k=dfs(v,min(dis,e[i].dis));
if(k>0){
e[i].dis-=k;
e[i^1].dis+=k;
return k;
}
}
}
return 0;
}
int main(){
n=read();m=read();
s=read();t=read();
int u,v,w;
for(int i=1;i<=m;i++){
u=read();v=read();w=read();
add(u,v,w);add(v,u,0);
}
int d,ans=0;
while(bfs()){
for(int i=1;i<=n;i++)cur[i]=head[i];
while(d=dfs(s,inf)) ans+=d;
}
printf("%d",ans);
return 0;
}
对于当前弧优化,我是懵的,只能说背背板子rwr;
如何用网络流解决二分图匹配问题:
首先我们需要建立一个超级源点S,还要建立一个超级汇点T;
然后我们将所有在左边的点和源点S连一条长度为1的边,将所有在右边的点和汇点T连一条长度为1的边,对于中间的边,我们按照输入加入长度为1的边,然后跑一边由S到T的网络最大流就可以得到结果啦。
//网络流做法:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
const int mxn=10010;
const int mxm=1000010;
const int inf=2147483647;
int n,m,ee;
int s,t;
struct node{
int to,dis,nxt;
}e[mxm<<1];
int ecnt=1,head[mxn],dep[mxn],cur[mxn];
void add(int from,int to,int dis){
ecnt++;
e[ecnt].to=to;
e[ecnt].dis=dis;
e[ecnt].nxt=head[from];
head[from]=ecnt;
}
bool bfs(){
queue<int> q;
while(!q.empty()) q.pop();
memset(dep,0,sizeof(dep));
q.push(s);
dep[s]=1;
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u],v;i;i=e[i].nxt){
v=e[i].to;
if(e[i].dis>0&&!dep[v]){
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t]==0) return 0;
return 1;
}
int dfs(int u,int dis){
if(u==t) return dis;
for(int &i=cur[u];i;i=e[i].nxt){
int v=e[i].to;
if(dep[v]==dep[u]+1&&e[i].dis!=0){
int k=dfs(v,min(dis,e[i].dis));
if(k>0){
e[i].dis-=k;
e[i^1].dis+=k;
return k;
}
}
}
return 0;
}
int main(){
n=read();m=read();
ee=read();
int u,v;
int nn=n+m+2;
for(int i=1;i<=n;i++){
add(1,i+1,1);
add(i+1,1,0);
}
for(int i=1;i<=ee;i++){
u=read();v=read();
if(u<=n&&v<=m){
add(u+1,v+n+1,1);
add(v+n+1,u+1,0);
}
}
for(int i=1;i<=m;i++){
add(i+n+1,nn,1);
add(nn,i+n+1,0);
}
s=1;t=nn;
int d,ans=0;
while(bfs()){
for(int i=1;i<=nn;i++)cur[i]=head[i];
while(d=dfs(s,inf)) ans+=d;
}
printf("%d",ans);
return 0;
}
//匈牙利算法:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int ans=0;
char last=' ',ch=getchar();
while(ch>'9'||ch<'0') last=ch,ch=getchar();
while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
if(last=='-') ans=-ans;
return ans;
}
const int mxn=1010;
const int mxm=1010;
int n,m,e;
int girl[mxm],f[mxn][mxm],vis[mxm];
bool work(int u){
for(int i=1;i<=m;i++){
if(!vis[i]&&f[u][i]){
vis[i]=1;
if(!girl[i]||work(girl[i])){
girl[i]=u;
return 1;
}
}
}
return 0;
}
int ans;
int main(){
n=read();m=read();e=read();
int u,v;
for(int i=1;i<=e;i++){
u=read();
v=read();
if(u>n||v>m) continue;
f[u][v]=1;
}
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
ans+=work(i);
}
printf("%d",ans);
return 0;
}