网络流(入门版)
Dinic 网络最大流
若存在一条从源点到汇点的新的增广路,设 \(k\) 为该增广路上最小的残留流量,那么整张图的流量就会增加 \(k\)。这个过程可以通过搜索模拟。
但是这样的话可能会因为搜索顺序导致最后的答案并非最优解,可能某些小流量占用了流量大的边。于是就有了建反边的操作。
反边即边 \(u\rightarrow v\) 变为 \(v\rightarrow u\),初始容量为 \(0\)。当正边容量减 \(k\) 后,反边容量增加 \(k\),走反边的退流操作相当于占用先前增广路的后半程,使先前的增广路去搜索新的增广路。这样就实现了一个“反悔”的操作
深搜每次可以找到一条增广路,这就是 EK 算法,复杂度为 \(O(nm^2)\)。但每次只找一条效率太低了。
Dinic 算法每次先 bfs 给图分层,之后在分层图上 dfs 。这样 dfs 的过程中可以找到多条增广路,复杂度 \(O(n^2m)\)
当前弧优化:在 dfs 的过程中,每条边只会出现在一条增广路中,所以在下次遍历中从它的下一条边开始遍历即可。这得上链前,邻接表很难写
代码:
bool bfs()
{
queue <int> q;
for (int i=1;i<=t;i++) dep[i]=0,cur[i]=hd[i];
dep[s]=1; q.push(s);
while (!q.empty())
{
int u=q.front(); q.pop();
for (int i=hd[u],v;i;i=e[i].nxt)
{
if (dep[v=e[i].to]||e[i].val<=0) continue;
dep[v]=dep[u]+1; q.push(v);
if (v==t) return true;
}
}
return false;
}
int dfs(int u,int val)
{
if (u==t) return val;
int k=0,tmp,v;
for (int i=cur[u];i;i=e[i].nxt)
{
if (dep[v=e[i].to]!=dep[u]+1||e[i].val<=0) continue;
cur[u]=i;
tmp=dfs(v,min(val,e[i].val));
if (!tmp) { dep[v]=0; continue; }
k+=tmp,val-=tmp;
e[i].val-=tmp,e[i^1].val+=tmp;
}
return k;
}
void dinic() { while (bfs()) ans+=dfs(s,inf); }
最小费用最大流
其实就是将 bfs 改为 spfa。每次寻找从源点到汇点费用最小的一条增广路并进行增广。反边的费用是正边费用的相反数
代码懒得放。一般写 EK 就足够了
最小割
最小割即将原图的点集分为 \(S,T\) 两个集合,连接两个集合之间的所有边的容量。最大流等于最小割,证明见 wiki
例题
P4313 文理分科
正难则反,考虑割边。选文理是二选一模型,非常好搞;考虑割集的意义,给每个点掰一下表示同时选文/理的权值然后给对应的点连边即可。
P2766 最长不下降子序列问题
点内的限制就拆点完成,其他的直接根据题意跑最大流即可
P3980 [NOI2008] 志愿者招募
建模留坑。懒得写了。该题中一个增广路的流量要按最大的算,直接给容量取相反数+偏移量正常跑费用流即可
P3159 [CQOI2012] 交换棋子
网络流还是太抽象了。
拆点搞交换次数的限制,八连通连边表过程,从原位置到目标位置连边表目标,没了。
P2762 太空飞行计划问题
最大权闭合子图模型。按照最小割的思想处理即可。负点权转换为 \(a-b\) 的形式就可以转为正权,然后跑最大流就没了
P3163 [CQOI2014] 危桥
多源多汇网络流。
一个想法是建立超级源点和超级汇点连接各个源点、汇点,边的容量为目标流量。但可能会出现 \(a1\rightarrow b2,b1\rightarrow a2\) 的情况。其实这种情况跑两遍网络流,第二次交换 \(b1,b2\) 就能做。
现在设 \(a1\rightarrow b2\) 的流量为 \(x\),\(b1\rightarrow a2\) 的流量也为 \(x\),那么 \(a1\rightarrow a2\) 的流量即为 \(c1-x\),\(b1\rightarrow b2\) 的流量为 \(c2-x\)。
在跑第二遍网络流的过程中,因为图是无向图,所以可以视为 \(a1\rightarrow a2\) 与 \(b1\rightarrow b2\) 的流量不变。也就是说,存在流量为 \(x\) 的路径 \((a1,b2),(a1,b1),(b1, a2),(b2, a2)\) 。然后就可以愉快地搞 \((a1,a2),(b1,b2)\) 流量为 \(x\) 的路径了
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=55;
const int M=3000;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,s1,t1,s2,t2,c1,c2;
string c[N];
struct Edge { int to,nxt,val; }e[M<<1];
int hd[N],cnt=1,s,t,ans;
void add(int u,int v,int val)
{
e[++cnt]={v,hd[u],val},hd[u]=cnt;
e[++cnt]={u,hd[v],0},hd[v]=cnt;
}
void build(int ss,int tt)
{
memset(hd,0,sizeof hd); cnt=1;
s=n+1,t=n+2;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{
if (c[i][j]=='O') add(i,j,1);
else if (c[i][j]=='N') add(i,j,inf);
}
add(s,s1,c1); add(s,ss,c2);
add(t1,t,c1); add(tt,t,c2);
}
int dep[N],cur[N];
bool bfs()
{
queue <int> q;
for (int i=1;i<=t;i++) dep[i]=0,cur[i]=hd[i];
q.push(s); dep[s]=1;
while (!q.empty())
{
int u=q.front(); q.pop();
for (int i=hd[u],v;i;i=e[i].nxt)
{
if (dep[v=e[i].to]||e[i].val<=0) continue;
dep[v]=dep[u]+1; q.push(v);
if (v==t) return true;
}
}
return false;
}
int dfs(int u,int val)
{
if (u==t) return val;
int k=0,tmp,v;
for (int i=cur[u];i&&val;i=e[i].nxt)
{
cur[u]=i;
if (e[i].val<=0||dep[v=e[i].to]!=dep[u]+1) continue;
tmp=dfs(v,min(val,e[i].val));
if (!tmp) { dep[v]=0; continue; }
k+=tmp,val-=tmp;
e[i].val-=tmp,e[i^1].val+=tmp;
}
return k;
}
int dinic()
{
int res=0;
while (bfs()) res+=dfs(s,inf);
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
while (cin>>n>>s1>>t1>>c1>>s2>>t2>>c2)
{
s1++,t1++,s2++,t2++; ans=1;
for (int i=1;i<=n;i++) { cin>>c[i]; c[i]=" "+c[i]; }
build(s2,t2); ans&=(dinic()==c1+c2);
build(t2,s2);ans&=(dinic()==c1+c2);
cout<<(ans?"Yes\n":"No\n");
}
return 0;
}

浙公网安备 33010602011771号