0.前言
我之前写过三篇共一系列的网络流随记,如果有没看过的同学可以先去看一下。
但是自己在做各种网络流题时又发现了一些没有写进前三篇博客里的知识点,在此做一个简单的整理(真的仅仅是整理,有些十分奇妙的做法我也不会证)。
1.上下界网络流
普通的最大流是相当于给出了一个流量上界,那么现在我们加入流量下界,其实解法是差不多的。
1.0 无源汇上下界可行流
题目已经说得很清楚要干什么了。我们想要去除多出来的下界,于是先满足每个点的下界,但是这样显然可能造成流量不平衡(有的点多流入、有的点多流出)。于是我们把不平衡的流量转移到源点和汇点(本就应该不平衡),这样就可以让它们承担不平衡的锅而不用担心出错了。具体地说,每个点先满足下界,统计流入流出之差,多流入的从源点连,多流出的连到汇点。
1.1 有源汇上下界可行流
现在多出了两个流量不平衡的点,刚刚的分析不能直接套入。但是我们发现源点流出的一定等于汇点流入的,所以我们从汇点到源点连一条流量正无穷的边来假装它们流量平衡,这样就可以跑 (1.0) 了。
1.2 有源汇上下界最大流
可行流怎么增大?找增广路!现在我们把虚拟源点和虚拟汇点跑满了,那么就再从原图的源点到汇点跑一遍(注意删去容量正无穷的边)。
1.3 有源汇上下界最小费用可行流
方法与 (1.1) 类似,但是虚拟源点和虚拟汇点连的边费用为 (0),其他边费用不变。这样先满足下界算出的费用,然后跑费用流即可。
1.4 习题
P1.0 洛谷P5192 【模板】有源汇上下界最大流
P1.1 洛谷P4043 [AHOI2014/JSOI2014]支线剧情
2.最小割树(Gomory-Hu Tree)
有时,我们想要求出图中任意两点之间的最小割,这时暴力就不行了,需要效率更高的算法。
我们发现,朴素的算法枚举了所有点对,而这些点对之间的最小割有一些是重复的,最小割树就解决了这个问题。它的定义是:
对于每一条边 ((u,v)),它的权值为原图 (u,v) 间最小割的值,且这条边恰好将最小割树分成原图上最小割所分割的两个点集。
容易发现这棵树上 (u,v) 之间最小权值即为原图中最小割,所以可以树上倍增求得。那么我们怎样构造这棵树呢?
我们随便取两个点求最小割,连边,再递归进分成的两个点集处理即可!这样,我们就把求最小割的次数从 (O(n^2)) 减为了 (O(n)),可以支持一般的查询。
代码如下:
const int N=510,M=1510;
int n,m,q;
struct Graph {
struct Edge {
int to,nxt,wei;
}e[M<<2];
int hd[N],cnt=-1;
il void ade(int u,int v,int w){
e[++cnt].to=v,e[cnt].wei=w;
e[cnt].nxt=hd[u],hd[u]=cnt;
e[++cnt].to=u,e[cnt].wei=0;
e[cnt].nxt=hd[v],hd[v]=cnt;
}
int dep[N];
il bool BFS(int s,int t){
memset(dep,0,sizeof(dep));
queue<int> q;
q.push(s),dep[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(rg int i=hd[u];~i;i=e[i].nxt){
int v=e[i].to;
if(!dep[v]&&e[i].wei){
q.push(v),dep[v]=dep[u]+1;
}
}
}
return dep[t];
}
int DFS(int u,int t,int flin){
if(u==t)return flin;
int flout=0;
for(rg int i=hd[u];~i&&flin;i=e[i].nxt){
int v=e[i].to,w=e[i].wei;
if(dep[v]==dep[u]+1&&w){
int mxfl=DFS(v,t,min(flin,w));
if(!mxfl){
dep[v]=-1;
continue;
}
e[i].wei-=mxfl,e[i^1].wei+=mxfl;
flin-=mxfl,flout+=mxfl;
}
}
return flout;
}
int Dinic(int s,int t){
for(rg int i=0;i<=cnt;i+=2){
e[i].wei+=e[i^1].wei,e[i^1].wei=0;
}
int res=0;
while(BFS(s,t))res+=DFS(s,t,INF);
return res;
}
}G;
struct Tree {
struct Edge {
int to,nxt,wei;
}e[M<<1];
int hd[N],cnt;
il void ade(int u,int v,int w){
e[++cnt].to=v,e[cnt].wei=w;
e[cnt].nxt=hd[u],hd[u]=cnt;
e[++cnt].to=u,e[cnt].wei=w;
e[cnt].nxt=hd[v],hd[v]=cnt;
}
int fa[N][10],dep[N],d[N][10];
int v[N],t1[N],t2[N];
void Build(int l,int r){
if(l==r)return;
int s=v[l],t=v[l+1];
ade(s,t,G.Dinic(s,t));
int c1=0,c2=0;
for(rg int i=l;i<=r;i++){
if(G.dep[v[i]])t1[++c1]=v[i];
else t2[++c2]=v[i];
}
for(rg int i=l;i<=l+c1-1;i++)v[i]=t1[i-l+1];
for(rg int i=l+c1;i<=r;i++)v[i]=t2[i-c1-l+1];
Build(l,l+c1-1),Build(l+c1,r);
}
void DFS(int u,int ff,int wei){
fa[u][0]=ff,dep[u]=dep[ff]+1,d[u][0]=wei;
for(rg int i=1;i<=9;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
d[u][i]=min(d[u][i-1],d[fa[u][i-1]][i-1]);
}
for(rg int i=hd[u];i;i=e[i].nxt){
int v=e[i].to;
if(v!=ff)DFS(v,u,e[i].wei);
}
}
il int LCA(int u,int v){
int res=INF;
if(dep[u]<dep[v])swap(u,v);
for(rg int i=9;i>=0;i--){
if(dep[fa[u][i]]>=dep[v]){
res=min(res,d[u][i]),u=fa[u][i];
}
}
if(u==v)return res;
for(rg int i=9;i>=0;i--){
if(fa[u][i]!=fa[v][i]){
res=min(res,min(d[u][i],d[v][i]));
u=fa[u][i],v=fa[v][i];
}
}
return min(res,min(d[u][0],d[v][0]));
}
void Init(){
for(rg int i=1;i<=n;i++)v[i]=i;
Build(1,n),DFS(1,0,0);
}
}T;
int main(){
Read(n),Read(m);
memset(G.hd,-1,sizeof(G.hd));
for(rg int i=1,u,v,w;i<=m;i++){
Read(u),Read(v),Read(w);
G.ade(u,v,w),G.ade(v,u,w);
}
T.Init(),Read(q);
for(rg int i=1,u,v;i<=q;i++){
Read(u),Read(v);
cout<<T.LCA(u,v)<<endl;
}
KafuuChino HotoKokoa
}
双倍经验:
P2.0 洛谷P4897 【模板】最小割树
P2.1 洛谷P4123 [CQOI2016]不同的最小割
3.完结撒花
屑作者直到现在依然懒得学 ISAP 和 HLPP,于是等着被毒瘤出题人卡 Dinic
祝各位遇到网络流题直接爆切!