网络流=带反悔的贪心。——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
",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}
floor,w_2=lfloor frac{m+1}{2}
floor) ;若 (m_{i,j}) 为奇数且 ((i,j)) 初状态为0,末状态为1, (w_1=lfloor frac{m+1}{2}
floor,w_2=lfloor frac{m}{2}
floor) ;否则 (w_1=frac{m}{2},w_2=frac{m}{2}) 。