參考文章:
1. 博客園:Dinic算法(研究總結,網絡流)
2. 洛谷博客:網絡最大流-從入門開始,詳細講到實用易懂的Dinic算法
本文主要是用 Dinic算法 解決最大流問題。
洛谷 P3376 【模板】網絡最大流
最大流問題,Dinic算法,最壞時間複雜度爲O(V2*E),然而一般情況下是達不到這麼大的,要不這題早超時了。
算法流程
1、根據殘量網絡計算層次圖。
2、在層次圖中使用DFS進行增廣直到不存在增廣路。
3、重複以上步驟直到無法增廣。
時間複雜度
因爲在Dinic的執行過程中,每次重新分層,匯點所在的層次是嚴格遞增的,而n個點的層次圖最多有n層,所以最多重新分層n次。假設總共有m條邊,在同一個層次圖中,因爲每條增廣路都有一個瓶頸,而兩次增廣的瓶頸不可能相同,所以增廣路最多m條。搜索每一條增廣路時,前進和回溯都最多n次,所以這兩者造成的時間複雜度是O(nm);綜上所述,Dinic算法時間複雜度的理論上界是O(n*nm)=O(n2*m)。
簡單來說,Dinic算法就是先進行BFS,判斷是否有增廣路(能從源點s達到匯點t)並且記錄圖中每個點所在的廣度優先搜索生成樹的層數,形成分層圖;如果有增廣路,則進行DFS,限制只能從層數小的向層數大的拓展,如果能找到終點並且返回時流量flow大於0,則每條正向邊減去增廣路中的最小流量,每條反向邊加上增廣路中的最小流量(這是算法的關鍵,引入反向邊就是爲了“反悔”,或者說“糾正之前可能錯誤的流量路徑”)。
以下的代碼,是BFS判斷是否有增廣路,DFS每次只能搜索出一條增廣路。BFS分層之後可以多次循環DFS。
#include <bits/stdc++.h>
using namespace std;
//在普通情況下,DINIC算法時間複雜度爲O(V^2*E)
const int N=1e4+10,M=1e5+10,inf=1e9;
int n,m,s,t,cnt,head[N],dep[N];
struct edge
{
int to,w,next;
}e[M<<1];
void add(int x,int y,int z)
{
e[cnt].to=y;
e[cnt].w=z;
e[cnt].next=head[x];
head[x]=cnt++;
}
void add_edge(int x,int y,int z)
{
add(x,y,z);
add(y,x,0);//反向邊初始爲0
}
bool bfs()
{
queue<int>q;
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];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==0)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t])return 1;
return 0;
}
int dfs(int u,int flow)
{
if(u==t)return flow;//到達匯點,返回這條增廣路上的最小流量
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==dep[u]+1)
{
int di=dfs(v,min(flow,e[i].w));//min(flow,e[i].w)表示起點到v的最小流量
if(di>0)//防止dfs結果return 0的情況,如果di=0會死循環
{
e[i].w-=di;
e[i^1].w+=di;//反向邊加上di
return di;//di表示整條增廣路上的最小流量,回溯的時候一直向上返回,返回的di是不變的
}
}
}
return 0;//找不到增廣路,到不了匯點
}
int dinic()
{
int ans=0;
while(bfs())//先bfs進行“探路”並分層,分層後進行多次dfs
{
//每dfs一次,就嘗試找到一條增廣路並返回路上的最小流量
while(int d=dfs(s,inf))//while循環的意義:只要dfs不爲0,就一直dfs,直到找不到增廣路
ans+=d;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n>>m>>s>>t;
int x,y,z;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++)
{
cin>>x>>y>>z;
add_edge(x,y,z);
}
int ans=dinic();
printf("%d\n",ans);
return 0;
}
HDU 4280 Island Transport
模板題,但是Dinic算法需要優化才能過這題。
(1)加邊優化:題目是雙向邊,所以加反向邊的時候加的容量直接寫成與正向邊相同(而不是0),這樣每次就只加了2條邊。
這個加邊優化必須要寫。
void add_edge(int x,int y,int z)
{
add(x,y,z);
add(y,x,z);//而不是add(y,x,0);
}
(2)優化DFS:
- 當前弧優化,
for(int &i=cur[u];i!=-1;i=e[i].next)
- 多路增廣,不直接返回一條弧的流量,而是把所有弧都跑完或者沒有流量後再返回res
- 炸點,使沒有流量(不能再向後流出)的點不會再被訪問,標記爲
dep[u]=-2
//只優化DFS 時間:9516ms
int dfs(int u,int flow)
{
if(u==t||flow==0)return flow;//到達匯點或者是沒有流量,就返回流量
int res=0;
for(int &i=cur[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==dep[u]+1)
{
int di=dfs(v,min(flow,e[i].w));//min(flow,e[i].w)表示起點到v的最小流量
if(!di)continue;
e[i].w-=di;
e[i^1].w+=di;//反向邊加上di
res+=di;//res相當於從v點流出了多少流量(當前的流出流量作爲後面點的供給)
flow-=di;//flow相當於還可以流入多少流量到v點(給當前點的供給)
//多路增廣,不直接返回一條弧的流量,而是把所有弧都跑完或者沒有流量後再返回res
if(flow==0)break;
}
}
if(res==0)dep[u]=-2;//炸點,使沒有流量(不能再向後流出)的點不會再被訪問
//不寫這句話就會超時!
return res;
}
(3)還有一種玄學優化,就是優化BFS:在BFS中找到匯點就直接結束。(這個就相當於找到終點就不更新分層圖了,我感覺好像有點問題,但是幾個OJ測了都沒問題)
//只優化BFS 時間:8486ms
bool bfs()
{
queue<int>q;
memset(dep,0,sizeof(dep));
q.push(s);
dep[s]=1;
while(!q.empty())
{
int u=q.front();q.pop();
if(u==t)return 1;//這句話非常重要!!!
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==0)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t])return 1;
return 0;
}
(4) STL中的queue用數組模擬,能快400ms左右,在這題用處不大。
AC代碼:
//BFS,DFS同時優化 未優化STL隊列 時間:7675ms
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=1e5+10,inf=0x7f7f7f7f;
int T,n,m,s,t,cnt,head[N],dep[N],cur[N];
struct edge
{
int to,w,next;
}e[M<<1];
inline void add(int x,int y,int z)
{
e[cnt].to=y;
e[cnt].w=z;
e[cnt].next=head[x];
head[x]=cnt++;
}
inline void add_edge(int x,int y,int z)
{
add(x,y,z);
add(y,x,z);
}
bool bfs()
{
queue<int>q;
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];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==0)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t])return 1;
return 0;
}
int dfs(int u,int flow)
{
if(u==t||flow==0)return flow;//到達匯點或者是沒有流量,就返回流量
int res=0;
for(int &i=cur[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==dep[u]+1)
{
int di=dfs(v,min(flow,e[i].w));//min(flow,e[i].w)表示起點到v的最小流量
if(!di)continue;
e[i].w-=di;
e[i^1].w+=di;//反向邊加上di
res+=di;//res相當於從v點流出了多少流量(當前的流出流量作爲後面點的供給)
flow-=di;//flow相當於還可以流入多少流量到v點(給當前點的供給)
//多路增廣,不直接返回一條弧的流量,而是把所有弧都跑完或者沒有流量後再返回res
if(flow==0)break;
}
}
if(res==0)dep[u]=-2;//炸點,使沒有流量(不能再向後流出)的點不會再被訪問
//不寫這句話就會超時!
return res;
}
int dinic()
{
int ans=0;
while(bfs())//先bfs進行“探路”並分層,分層後進行多次dfs
{
for(int i=1;i<=n;i++)//比這個memcpy(cur,head,sizeof(head)) 要快一點
cur[i]=head[i];
while(int d=dfs(s,inf))//while循環的意義:只要dfs不爲0,就一直dfs,直到找不到增廣路
ans+=d;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin>>T;
while(T--)
{
cnt=0;
memset(head,-1,sizeof(head));
cin>>n>>m;
int x,y;
int mi=inf,mx=-inf;
for(int i=1;i<=n;i++)
{
cin>>x>>y;
if(x<mi)//找橫座標最小的點,是源點
{
mi=x;
s=i;
}
if(x>mx)//找橫座標最大的點,是匯點
{
mx=x;
t=i;
}
}
int a,b,w;
for(int i=1;i<=m;i++)
{
cin>>a>>b>>w;
add_edge(a,b,w);
}
int ans=dinic();
printf("%d\n",ans);
}
return 0;
}
/*
2
5 7
4 5
3 1
3 3
3 0
0 0
1 3 3
2 3 4
2 4 3
1 5 6
4 5 3
1 4 4
3 4 2
ans:9
*/
POJ 3281 Dining
我覺得這題的難點就是建圖,以及把這個看起來像二分圖匹配的問題轉化爲最大流問題(不看題解,真以爲是二分圖匹配)。
限制條件:每種食物和水都只能被一頭牛用一次,求最多能餵飽多少頭牛。
把一頭牛拆成兩頭(你沒看錯,就是這種拆點的神奇操作),然後建圖:食物->牛->牛->飲料,最後再加一個超級源點連上每種食物,加一個超級匯點連上每種飲料,圖中的任意一條增廣路就變成了:超級源點->食物->牛(左)->牛(右)->飲料->超級匯點。
建立網絡流模型:
1.對每種食物建立從源點指向它的一條邊,流量爲1
2.在牛與它所喜愛的食物間建立一條邊,流量爲1
3.在牛與它所喜歡的飲料間建立一條邊,流量爲1
4.對每種飲料建立一條指向匯點的邊,流量爲1
5.在上面的基礎上,將牛拆點,在拆開的點間建立一條流量爲1的邊
在以上條件下,從源點到匯點的最大流即爲答案
爲什麼要這樣建圖?原因如下:
模型的分析:
條件1使得滿足每種食物有且只有一個,條件4類似
條件2使得每頭牛隻能選擇自己喜歡的食物,條件3類似
條件5使得每頭牛最多隻能選擇一種飲料和食物
還注意一個小細節:對牛的編號、食物編號、飲料編號要進行處理,防止它們編號重合。設食物 f 種,飲料 d 種,牛 n 頭,則設左邊牛的編號爲[1,n]不變,拆點得到的右邊牛的編號在原有基礎上 +n ,食物編號在原有基礎上 +2n ,飲料編號在原有基礎上 +2n+f 。源點 s 編號爲0,匯點 t 編號爲2n+f+d+1。
AC代碼:
//#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int N=1e3+10,M=1e5+10,inf=1e9;
bool vis1[N],vis2[N];
int n,s,t,cnt,head[N],dep[N];
struct edge
{
int to,w,next;
}e[M<<1];
void add(int x,int y,int z)
{
e[cnt].to=y;
e[cnt].w=z;
e[cnt].next=head[x];
head[x]=cnt++;
}
void add_edge(int x,int y,int z)//加正向邊和初始的反向邊
{
add(x,y,z);
add(y,x,0);
}
bool bfs()
{
queue<int>q;
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];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==0)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t])return 1;
return 0;
}
int dfs(int u,int flow)
{
if(u==t)return flow;
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==dep[u]+1)
{
int di=dfs(v,min(flow,e[i].w));
if(di>0)
{
e[i].w-=di;
e[i^1].w+=di;
return di;
}
}
}
return 0;
}
int dinic()
{
int ans=0;
while(bfs())
{
while(int d=dfs(s,inf))
ans+=d;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
memset(head,-1,sizeof(head));
int f,d,q1,q2,x,y;
cin>>n>>f>>d;
s=0,t=2*n+f+d+1;
for(int i=1;i<=n;i++)
{
cin>>q1>>q2;
int cow1=i;//左邊牛的編號
int cow2=i+n;//右邊牛的編號(拆點)
while(q1--)
{
cin>>x;//食物
x+=2*n;
if(!vis1[x])
{
add_edge(s,x,1);//源點與食物連邊
vis1[x]=1;
}
add_edge(x,cow1,1);//食物與牛連邊
}
add_edge(cow1,cow2,1);//牛與牛自身連邊
while(q2--)
{
cin>>y;//飲料
y+=2*n+f;
add_edge(cow2,y,1);//牛與飲料連邊
if(!vis2[y])
{
vis2[y]=1;
add_edge(y,t,1);//飲料與匯點連邊
}
}
}
int ans=dinic();
printf("%d\n",ans);
return 0;
}
POJ 3436 ACM Computer Factory
題意:
有N臺組裝電腦的機器。電腦的組成部分共有P部分。
每臺機器有P個輸入輸出規格。還有一個容量Q;
其中輸入規格有三種情況:0,1,2
0:該部分不能存在
1:該部分必須保留
2:該部分可有可無
輸出規格有2種情況:0,1
0:該部分不存在
1:該部分存在
要求的是生產電腦最大的臺數,就是求網絡中的最大流。
建圖:
- 要增加源點和匯點。輸入沒有1的連接源點,輸出全部是1的連接匯點。
- 考慮到每臺機器都有容量,所以把一臺機器分成兩個點,中間建一條容量的邊。
- 如果一臺機器的輸出符合另一臺機器的輸入(只要不是0->1和1->0就符合),則建一條容量無窮大的邊。
最後考慮處理輸出邊數問題,只要考慮退回邊和原邊的關係,退回邊的權值就是原邊流過的流量值,那麼遍歷所有反向邊(邊的編號爲奇數的),考慮其是否>0,如果是,則說明這條邊有流流過,那麼對應將其記錄下來一起輸出即可。
這題wa了無數次,最後發現是輸出的順序錯了,寫成了原料到產出,答案必須是寫產出到原料,我以爲這個順序沒關係(If several solutions exist, output any of them. 題意應該是說答案的行的順序可以互換,沒說行的內部順序可以寫反)
AC代碼:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
using namespace std;
const int N=52,M=1e5+10,P=12,inf=2e9;
int s,t,p,n,w,num,cnt,head[N],dep[N];
int a[N][P],b[N][P];//兩個下標是行號和列號
struct node
{
int u,v,w;
};
vector<node>path;
struct edge
{
int from,to,w,next;
}e[M<<1];
void add(int x,int y,int z)
{
e[cnt].from=x;
e[cnt].to=y;
e[cnt].w=z;
e[cnt].next=head[x];
head[x]=cnt++;
}
void add_edge(int x,int y,int z)//加正向邊和初始的反向邊
{
add(x,y,z);
add(y,x,0);
}
bool bfs()
{
queue<int>q;
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];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==0)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t])return 1;
return 0;
}
int dfs(int u,int flow)
{
if(u==t)return flow;
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==dep[u]+1)
{
int di=dfs(v,min(flow,e[i].w));
if(di>0)
{
e[i].w-=di;
e[i^1].w+=di;
return di;
}
}
}
return 0;
}
int dinic()
{
int ans=0;
while(bfs())
{
while(int d=dfs(s,inf))
ans+=d;
}
return ans;
}
bool judge(int numa,int numb)//判斷從某個機器的產出能否連到其他機器的原料
{
for(int i=1;i<=p;i++)
{
int x=a[numa][i];
int y=b[numb][i];
if((x==0&&y==1)||(x==1&&y==0))return 0;
}
return 1;
}
int main()
{
ios::sync_with_stdio(false);
cin>>p>>n;
memset(head,-1,sizeof(head));
s=0,t=2*n+1;
for(int i=1;i<=n;i++)
{
cin>>w;
int sum1=0;//統計每個原料結點或者產出結點中1的個數
int numa=i;//原料結點編號
int numb=i+n;//產出結點編號
for(int j=1;j<=p;j++)
{
cin>>a[i][j];
if(a[i][j]==1)sum1++;
}
if(sum1==0)add_edge(s,numa,inf);//將源點與不存在1的原料結點連邊
add_edge(numa,numb,w);//將同一機器的原料結點和產出結點連邊
sum1=0;
for(int j=1;j<=p;j++)
{
cin>>b[i][j];
if(b[i][j]==1)sum1++;
}
if(sum1==p)add_edge(numb,t,inf);//將全是1的產出結點與匯點連邊
}
for(int i=1;i<=n;i++)//原料結點行號
{
for(int j=1;j<=n;j++)//產出結點行號
{
if(i==j)continue;
if(judge(i,j))
{
//printf("link edge j=%d i=%d\n",j,i);
add_edge(j+n,i,inf);//將機器j的產出結點連到機器i的原料結點
}
}
}
int ans=dinic();
printf("%d ",ans);
for(int i=1;i<=cnt;i+=2)//遍歷反向邊
{
int from=e[i].from;
int to=e[i].to;
int w=e[i].w;
if(w>0&&from>=1&&from<=n&&to>=n+1&&to<=2*n&&from!=to-n)
path.push_back({to-n,from,w});
//就這個地方我寫成了path.push_back({from,to-n,w}),wa了無數次!
}
printf("%d\n",path.size());
for(int i=0;i<path.size();i++)
printf("%d %d %d\n",path[i].u,path[i].v,path[i].w);
return 0;
}
/*
3 4
15 0 0 0 0 1 0
10 0 0 0 0 1 1
30 0 1 2 1 1 1
3 0 2 1 1 1 1
ans:
25 3
1 3 15(不能寫成3 1 15)
2 3 7
2 4 3
*/
POJ 1087 A Plug for UNIX
題意:
-
在一個會議室裏有n個插座(可能重複);
-
每個插座只能插一個電器的插頭(或者適配器);
-
有m個電器,每個電器有一個插頭需要插在相應一種插座上;
-
不是所有電器都能在會議室找到相應插座;
-
有k種適配器,每種適配器有無限多數量;
-
每種適配器(s1, s2)可以把s1類插座變爲s2類插座;
-
問最後最少有多少個電器無法使用。
建圖:
-
各個電器各自爲一個節點,和源點(不存在,自己構造)相連,且源點到電器的容量爲1。
-
將室內已有的插座各自爲一個節點,和匯點(不存在,自己構造)相連,且到匯點的容量爲1。
-
將電器到其應該插的插座類型的點做邊,且容量爲1。
-
將適配器(a, b)從a點到b點做邊,且邊的容量爲INF。
需要注意,在輸入電器以及輸入適配器的過程中,如果遇到室內沒有的插座類型,則需要在圖中添加新的節點。
樣例輸入:
4
A
B
C
D
5
laptop B
phone C
pager B
clock B
comb X
3
B X
X A
X D
畫圖理解樣例:
AC代碼:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <map>
#include <queue>
using namespace std;
const int N=1e4,M=1e5+10,inf=0x7f7f7f7f;
int n,m,k,s,t,cnt,head[N+10],dep[N+10];
map<string,int>vis;//存放電器的插頭和提供的插座(包括適配器轉換得到的插座)
struct edge
{
int to,w,next;
}e[M<<1];
void add(int x,int y,int z)
{
e[cnt].to=y;
e[cnt].w=z;
e[cnt].next=head[x];
head[x]=cnt++;
}
void add_edge(int x,int y,int z)
{
add(x,y,z);
add(y,x,0);
}
bool bfs()
{
queue<int>q;
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];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==0)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
if(dep[t])return 1;
return 0;
}
int dfs(int u,int flow)
{
if(u==t)return flow;//到達匯點,返回這條增廣路上的最小流量
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].w>0&&dep[v]==dep[u]+1)
{
int di=dfs(v,min(flow,e[i].w));//min(flow,e[i].w)表示起點到v的最小流量
if(di>0)//防止dfs結果return 0的情況,如果di=0會死循環
{
e[i].w-=di;
e[i^1].w+=di;//反向邊加上di
return di;//di表示整條增廣路上的最小流量,回溯的時候一直向上返回,返回的di是不變的
}
}
}
return 0;//找不到增廣路,到不了匯點
}
int dinic()
{
int ans=0;
while(bfs())//先bfs進行“探路”並分層,分層後進行多次dfs
{
//每dfs一次,就嘗試找到一條增廣路並返回路上的最小流量
while(int d=dfs(s,inf))//while循環的意義:只要dfs不爲0,就一直dfs,直到找不到增廣路
ans+=d;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
string s1,s2;
memset(head,-1,sizeof(head));
s=0,t=N;//源點s,匯點t(匯點編號大於所有點的個數就行)
cin>>n;
int num=0;
for(int i=1;i<=n;i++)//遍歷插座(可能有重複名字)
{
cin>>s1;//已提供的插座名字s1
if(!vis[s1])vis[s1]=++num;
//合併相同名字的插座,只留插座第一次出現的名字,其對應的編號爲vis[s1]
add_edge(vis[s1],t,1);//插座與匯點連邊,容量爲1
}
cin>>m;
for(int i=1;i<=m;i++)//遍歷插頭(可能有重複)
{
cin>>s1>>s2;//電器名s1,電器對應的插頭名s2
if(!vis[s2])vis[s2]=++num;
add_edge(s,vis[s2],1);//源點與插頭連邊,容量爲1
}
cin>>k;
for(int i=1;i<=k;i++)
{
cin>>s1>>s2;//插座s1可以轉換到插座s2
if(!vis[s1])vis[s1]=++num;//已經存在的名字中找不到s1,則將s1插入
if(!vis[s2])vis[s2]=++num;
//printf("link edge %d %d\n",vis[s1],vis[s2]);
add_edge(vis[s1],vis[s2],inf);//將可以轉換的兩個插座相連,容量爲無限
}
int maxflow=dinic();
printf("%d\n",m-maxflow);
return 0;
}
/*
16
A
D
X
A
D
A
D
X
D
A
D
D
X
D
X
D
14
CLOCK B
CLOCK B
CLOCK B
LAPTOP B
LAPTOP B
LAPTOP B
LAPTOP B
LAPTOP B
LAPTOP B
PAGER B
PAGER B
COMB X
CELL C
CELL C
4
C D
X D
B X
B A
ans:0
*/
UPD 2020.4.12
LOJ 127 / 洛谷 P4722【模板】最大流 加強版 / 預流推進
解決最大流問題有很多算法,諸如EK,Dinic,ISAP,HLPP等等。
今天看到了一篇很好的文章,比對了各個算法的適用場景和複雜度,可以看一下這篇博客:P4722 -【模板】最大流 加強版 / 預流推進
一般來說,Dinic算法是夠用的,但是這個加強版的題數據很毒瘤,得用HLPP。
我先挖個坑,看什麼時候HLPP算法給填了
看到一個用Dinic優化了一下卡過去的,(不是我寫的)代碼:
//C++ 17
#include<bits/stdc++.h>
using namespace std;
const int maxn=1210,maxm=120010;
struct edge
{
int u,v,cap;
}e[maxm];
struct Dinic
{
int tp,s,t,dis[maxn],cur[maxn],que[maxn];
vector<edge>e;vector<int>v[maxn];
void AddEdge(int x,int y,int flw)
{
e.push_back(edge{x,y,flw});
e.push_back(edge{y,x,0});
v[x].push_back(e.size()-2);
}
int bfs()
{
memset(dis,0x3f,sizeof(dis));
int l=1,r=1;que[1]=s;dis[s]=0;
while(l<=r)
{
int p=que[l++],to;
for(int i:v[p])if(e[i].cap&&dis[to=e[i].v]>1e9)
dis[to]=dis[p]+1,que[++r]=to;
}
return dis[t]<1e9;
}
int dfs(int p,int a)
{
if(p==t||!a)return a;
int sf=0,flw;
for(int &i=cur[p],to;i<(int)v[p].size();++i)
{
edge &E=e[v[p][i]];
if(dis[to=E.v]==dis[p]+1&&(flw=dfs(to,min(a,E.cap))))
{
E.cap-=flw;e[v[p][i]^1].cap+=flw;
a-=flw;sf+=flw;
if(!a)break;
}
}
return sf;
}
int dinic(int s,int t,int tp=1)
{
this->s=s;this->t=t;this->tp=tp;
int flw=0;
while(bfs())
{
memset(cur,0,sizeof(cur));
flw+=dfs(s,INT_MAX);
}
return flw;
}
}sol;
int n,m,i,s,t,ans;
int main(){
scanf("%d%d%d%d",&n,&m,&s,&t);
for(i=0;i<m;i++)scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].cap);
sort(e,e+m,[](edge a,edge b){return a.cap>b.cap;});
for(int tp:{0,1})
for(int p=1<<30,i=0;p;p/=2)
{
while(i<m&&e[i].cap>=p)
{
if(tp)sol.v[e[i].v].push_back(i*2+1);
else sol.AddEdge(e[i].u,e[i].v,e[i].cap);
i++;
}
ans+=sol.dinic(s,t,tp);
}
printf("%d\n",ans);
return 0;
}