網絡流=帶反悔的貪心。——517
個人認為網絡流=最大流dinic/費用流板子+玄學意會建圖。
網絡流朴素算法ek
對於每條邊 \((u,v,w)\) ,建一條相應的反向邊 \((v,u,0)\) 。
算法執行時,先從源點s bfs,看看到t最多能流多少,對於每個節點記錄它的前驅節點,如果到t的流量不為0,那么從t回溯回s,將每條邊的容量減去流量,其反向邊的容量加上流量,然后把答案加上所有回溯到的邊的流量;否則停止執行,返回結果。最壞復雜度 \(O(nm^2)\) 。
#include<bits/stdc++.h>
#define maxn 10005
#define maxm 100005
#define INF 1050000000
using namespace std;
template<typename tp>
void read(tp& x){
x=0;
char c=getchar();
bool sgn=0;
while((c<'0'||c>'9')&&c!='-')c=getchar();
if(c=='-')sgn=1,c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
if(sgn)x=-x;
}
template<typename tp>
void write(tp x){
if(x<0)putchar('-'),write(-x);
else{
if(x>=10)write(x/10);
putchar(x%10+'0');
}
}
struct edge{
int to,next,w;
}e[maxm<<1];
int head[maxn],cnte;
void add(int u,int v,int w){
e[++cnte].to=v;
e[cnte].w=w;
e[cnte].next=head[u];
head[u]=cnte;
}
int n,m,s,t,pre[maxn],edg[maxn],flow[maxn];
bool bfs(){
for(int i=1;i<=n;i++)pre[i]=edg[i]=-1,flow[i]=INF;
pre[s]=0;
queue<int> q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
if(u==t)break;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(e[i].w>0&&pre[v]==-1){
pre[v]=u;
edg[v]=i;
flow[v]=min(flow[u],e[i].w);
q.push(v);
}
}
}
return pre[t]!=-1;
}
int ek(){
int ans=0;
while(bfs()){
int x=t;
while(x!=s){
e[edg[x]].w-=flow[t];
e[edg[x]^1].w+=flow[t];
x=pre[x];
}
ans+=flow[t];
}
return ans;
}
signed main(){
read(n),read(m),read(s),read(t);
for(int i=1;i<=n;i++)head[i]=-1;
cnte=-1;
for(int i=1;i<=m;i++){
int u,v,w;
read(u);read(v);read(w);
add(u,v,w);
add(v,u,0);
}
write(ek());
return 0;
}
網絡流進階算法dinic
解釋不來,請上網搜索……
最壞復雜度 \(O(n^2m)\) 。
head1[]
:當前弧優化,極小部分題會卡,如LOJ117,加了快10倍
#include<bits/stdc++.h>
using namespace std;
const int maxn=10003,maxm=200003,INF=1050000000;
struct edge{int to,next,w;}e[maxm<<1];
int head[maxn],head1[maxn],cnte;
void add(int u,int v,int w){e[++cnte].to=v,e[cnte].w=w,e[cnte].next=head[u],head[u]=cnte;}
void addedge(int u,int v,int w){add(u,v,w),add(v,u,0);}
int n,s,t,dep[maxn],q[maxn];
bool bfs(){
for(int i=1;i<=n;i++)dep[i]=0,head1[i]=head[i];
dep[s]=1;
int *qhead=q,*qtail=q;
*qtail++=s;
while(qhead!=qtail){
int u=*qhead++;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(e[i].w&&dep[v]==0){
dep[v]=dep[u]+1;
*qtail++=v;
}
}
}
return dep[t]!=0;
}
int dfs(int u,int low){
if(low==0||u==t)return low;
int flow=0;
for(int &i=head1[u];~i;i=e[i].next){
int v=e[i].to;
if(e[i].w&&dep[v]==dep[u]+1){
int tmp=dfs(v,min(low,e[i].w));
if(tmp==0)dep[v]=0;
else{
flow+=tmp;
low-=tmp;
e[i].w-=tmp;
e[i^1].w+=tmp;
if(low==0)break;
}
}
}
return flow;
}
int dinic(){
int ans=0;
while(bfs())ans+=dfs(s,INF);
return ans;
}
int main(){
int m;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=n;i++)head[i]=-1;
cnte=-1;
while(m--){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
}
printf("%d",dinic());
return 0;
}
費用流朴素算法spfa+ek
對於每條邊 \((u,v,w,cost)\) ,建反向邊 \((v,u,w,-cost)\)
該算法即在最大流ek的基礎上把bfs改為spfa。為什么不能直接用dijkstra?因為有負環。
#include<bits/stdc++.h>
#define maxn 5005
#define maxm 100005
#define INF 1050000000
using namespace std;
struct edge{int to,next,w,cost;}e[maxm<<1];
int head[maxn],cnte;
void add(int u,int v,int w,int cost){e[++cnte].to=v,e[cnte].w=w,e[cnte].cost=cost,e[cnte].next=head[u],head[u]=cnte;}
void addedge(int u,int v,int w,int cost){add(u,v,w,cost),add(v,u,w,-cost);}
int n,s,t,pre[maxn],edg[maxn],flow[maxn],maxflow,dis[maxn],mincost;
bool vis[maxn];
bool spfa(){
for(int i=1;i<=n;i++)flow[i]=dis[i]=INF,vis[i]=0;
pre[t]=-1;
dis[s]=0;
queue<int> q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];~i;i=e[i].next){
int v=e[i].to;
if(e[i].w>0&&dis[u]+e[i].cost<dis[v]){
dis[v]=dis[u]+e[i].cost;
pre[v]=u;
edg[v]=i;
flow[v]=min(flow[u],e[i].w);
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
return pre[t]!=-1;
}
void micmxf(){
while(spfa()){
int x=t;
while(x!=s){
e[edg[x]].w-=flow[t];
e[edg[x]^1].w+=flow[t];
x=pre[x];
}
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
}
}
signed main(){
int m;
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=0;i<=n;i++)head[i]=-1;
cnte=-1;
for(int i=1;i<=m;i++){
int u,v,w,cost;
scanf("%d%d%d%d",&u,&v,&w,&cost);
addedge(u,v,w,cost);
}
micmxf();
printf("%d %d\n",maxflow,mincost);
return 0;
}
網絡流常用建圖方法
BZOJ1001
最朴素的建模。
直接在原圖上源點 \(s=1\) ,匯點 \(t=n*m\) 跑最大流即可。
網絡流24題 飛行員配對方案問題
理解最大流和二分圖匹配的關系。
本題構造一個二分圖,對於所有左側點 \(i\) ,連邊 \((s,i,INF)\) ;對於所有右側點 \(j\) ,連邊 \((j,t,INF)\) ,對於二分圖的每條邊,連邊 \((u,v,1)\) 。
網絡流24題 最小路徑覆蓋問題
最小路徑覆蓋。
先將每個點 \(u\) 拆成一個入點 \(u_1\) 和一個出點 \(u_2\) 。
\((s,i_1,1),(i_2,t,1),(u_2,v_1,1),(i_1,i_2,1)\)
求最大流。
NOI2006 最大獲利
對於每個物品有一個收益,選一些物品要付出一個代價,求最大收益的題目往往用最小割解決。
本題中,設 \(i\) 為中轉站, \((a_j,b_j,c_j)\) 為用戶需求,連邊 \((s,i,p_i),(j,t,c_j),(a_j,b_j,INF)\) ,然后求最大流(最小割),答案為所有收益的和-最小割。可以發現最小割=最小成本+舍棄的收益。
網絡流24題 方格取數問題
也用最小割解決。
先對圖黑白染色,然后設 \(i\) 為黑點, \(j\) 為白點, \(k\) 為與 \(i\) 相鄰的點,連邊 \((s,i,a_i),(j,t,a_j),(i,k,INF)\) 。
CQOI2009 跳舞
先考慮如何驗證給定的舞曲數目是否有解。
構造最大流,把每個人拆成兩個點,男孩 \(i_1,i_2\) ,女孩 \(i_3,i_4\) ,連邊 \((s,i_1,INF),(i_1,i_4,1),(i_1,i_2,k),(i_2,i_3,1),(i_3,i_4,k),(i_4,t,INF)\) ,如果最大流=舞曲數目*人數,那么可行,否則不可行。
然后我們只需要二分舞曲數目。
TJOI2015 線性代數
經過推算, \(D=\sum_{i=1}^n \sum_{j=1}^n a_i*a_j*b_{i,j}-\sum_{i=1}^n a_i*c_i\)
這和上面講的最小割類似,故用最小割解決。
連邊 \((s,i,c_i),(i,(i,j),INF),(j,(i,j),INF),((i,j),t,b_{i,j})\) 。
CQOI2012 交換棋子
費用流。
首先把每一個點拆成三個點 \(i_1,i_2,i_3\) 。
設 \(j\) 是與 \(i\) 八連通的點,若 \(i\) 初始狀態為1,連邊 \((s,i_2,1,0)\) ;若 \(i\) 末狀態為1,連邊 \((i_2,t,1,0)\) 。
對於所有節點,連邊 \((i_1,i_2,w_1,0),(i_2,i_3,w_2,1),(i_3,j_1,INF,0)\) ,其中若 \(m_{i,j}\) 為奇數且 \((i,j)\) 初狀態為1,末狀態為0, \(w_1=\lfloor \frac{m}{2} \rfloor,w_2=\lfloor \frac{m+1}{2} \rfloor\) ;若 \(m_{i,j}\) 為奇數且 \((i,j)\) 初狀態為0,末狀態為1, \(w_1=\lfloor \frac{m+1}{2} \rfloor,w_2=\lfloor \frac{m}{2} \rfloor\) ;否則 \(w_1=\frac{m}{2},w_2=\frac{m}{2}\) 。