最大流/最小割树/等价流树 学习笔记

最大流/最小割树/等价流树 学习笔记

最小割树 ( ext{Gomory-Hu Tree})

前置

约定无向图点数为(n),边数为(m)

割:断开一些边,使得(s,t)两点不连通

(lambda(u,v))(u,v)的最小割权值

在非负边权的无向图上使用网络流即可求得两点间的最小割,但是如果涉及查询所有点对的最小割,就需要进行(n^2)次网络流,复杂度很高

[ ]

简介

对于非负边权的无向图,适用于求出多点对之间的最小割/最大流的结构

1.( ext{Gomory-Hu Tree})的核心性质

构造树,使得树边((u,v))满足割掉这条边后,(u,v)的最小割对应将图分为树在两边的这两个集合

而边((u,v))的权值(w(u,v)=lambda(u,v))

2.求解最小割的方法

引理:

(lambda(a,b)ge min{lambda(a,c),lambda(c,b)})

假设(lambda(a,b) < min{lambda(a,c),lambda(c,b)})

(a,b)最小割的两个集合后两点所属的联通块集合为(A,B)

1.若(cin A),则(a,b)最小割也是(b,c)的割

2.若(cin B),则(a,b)最小割也是(a,c)的割

以上两种情况均与(lambda(a,b) < min{lambda(a,c),lambda(c,b)})矛盾

[ ]

假设要求(u,v)两点间的最短路,则答案就是(u,v)在树上路径的最小边权值,设其为边((s,t))

由上面的引理,显然有(lambda(u,v)gelambda(s,t))

而我们由( ext{Gomory-Hu Tree})的性质知道,(s,t)的割也是(u,v)的一个割,即(lambda(u,v)le lambda(s,t))

所以答案就是(lambda(s,t))

构建方法

构建( ext{Gomory-Hu Tree})最重要的一条引理,可以认为是最小割的"不交叉"性质

对于(s,t)最小割的一侧,设其点集为(W),则对于任意的(u,vin W),存在一个(s,t)最小割(X),满足(Xsube W)

具体的证明比较复杂,,但是这个性质确实非常巧妙

利用这个性质,可以得到( ext{Gomory-Hu Tree})不严谨的递归构造方法

1.对于当前点集(S),若(|S|=1),则结束递归

2.从(S)中选择两个点(x,y)求出最小割,设在割中(x,y)所属点集分别为(X,Y)

3.在( ext{Gomory-Hu Tree})上加入边((x,y,lambda(x,y))),递归解决子问题(Xcap S,Ycap S)

[ ]

实际在递归求解(S)的问题时,应该将图中其他的点缩点(这是论文里说的,实际没有人这么写)

是不是不缩点跑出来的树形是错的?

递归求解的次数为(O(n)),只需要求(O(n))次网络流即可

放一下丑陋的板子

#include<bits/stdc++.h>
using namespace std;
typedef pair <int,int> Pii;
#define pb push_back
#define mp make_pair
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
	T s=0; int f=0;
	while(!isdigit(IO=getchar())) if(IO=='-') f=1;
	do s=(s<<1)+(s<<3)+(IO^'0');
	while(isdigit(IO=getchar()));
	return f?-s:s;
}

const int N=510,M=6200,INF=1e9+10;

int n,m;
int U[M],V[M],W[M];

struct Edge{
	int to,nxt,w;
} e[M];
int head[N],ecnt;
void AddEdge(int u,int v,int w) {
	e[++ecnt]=(Edge){v,head[u],w};
	head[u]=ecnt;
}
void Link(int u,int v,int w){ AddEdge(u,v,w),AddEdge(v,u,w); }
#define erep(u,i) for(int i=head[u];i;i=e[i].nxt)
int dis[N],vc,S,T;
void clear(){ rep(i,1,vc) head[i]=0; ecnt=1,vc=0; }
int Bfs() {
	rep(i,1,vc) dis[i]=INF;
	static queue <int> que;
	dis[S]=0,que.push(S);
	while(!que.empty()) {
		int u=que.front(); que.pop();
		erep(u,i) {
			int v=e[i].to,w=e[i].w;
			if(!w || dis[v]<=dis[u]+1) continue;
			dis[v]=dis[u]+1,que.push(v);
		}
	}
	return dis[T]<INF;
}
int Dfs(int u,int in) {
	if(u==T) return in;
	int out=0;
	erep(u,i) {
		int v=e[i].to,w=e[i].w;
		if(!w || dis[v]!=dis[u]+1) continue;
		int t=Dfs(v,min(in-out,w));
		e[i].w-=t,e[i^1].w+=t,out+=t;
		if(in==out) break;
	}
	if(!out) dis[u]=0;
	return out;
}
int Dinic(){
	int ans=0;
	while(Bfs()) ans+=Dfs(S,INF);
	return ans;
}

int Mincut(int u,int v){
	clear(),vc=n,S=u,T=v;
	rep(i,1,m) Link(U[i],V[i],W[i]);
	return Dinic();
}

vector <Pii> G[N];
int P[N],R[N];
void Build(int l,int r) {
	if(l==r) return;
	int x=P[l],y=P[l+1];
	int w=Mincut(x,y);
	int p1=l-1,p2=r+1;
	rep(i,l,r) if(dis[P[i]]<INF) R[++p1]=P[i];
	else R[--p2]=P[i]; 
	rep(i,l,r) P[i]=R[i];
	G[x].pb(mp(y,w)),G[y].pb(mp(x,w));
	Build(l,p1),Build(p2,r);
}

int fa[N][10],s[N][10],dep[N];

void dfs(int u,int f) {
	for(int i=1;(1<<i)<=dep[u];++i) fa[u][i]=fa[fa[u][i-1]][i-1],s[u][i]=min(s[u][i-1],s[fa[u][i-1]][i-1]);
	for(Pii t:G[u]) if(t.first!=f) {
		int v=t.first,w=t.second;
		fa[v][0]=u,s[v][0]=w,dep[v]=dep[u]+1;
		dfs(v,u);
	}
}

int LCA(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	int mi=1e9;
	for(int i=0,del=dep[x]-dep[y];(1<<i)<=del;++i) if(del&(1<<i)) cmin(mi,s[x][i]),x=fa[x][i];
	if(x==y) return mi;
	drep(i,9,0) if(fa[x][i]!=fa[y][i]) cmin(mi,s[x][i]),cmin(mi,s[y][i]),x=fa[x][i],y=fa[y][i];
	cmin(mi,s[x][0]),cmin(mi,s[y][0]);
	return mi;
}

int main() {
	n=rd()+1,m=rd();
	rep(i,1,m) U[i]=rd()+1,V[i]=rd()+1,W[i]=rd();
	rep(i,1,n) P[i]=i; Build(1,n);
	dfs(1,0);
	rep(kase,1,rd()) printf("%d
",LCA(rd()+1,rd()+1));//printf("%d
",MinCut(rd()+1,rd()+1));
}




等价流树

等价流树的树形不需要满足( ext{Gomory-Hu Tree})的性质,只需要能够查询两点间的答案即可

在论文中看到的等价流树的非递归构建方法(伪代码)

(w_{1,..,n}=0,fa_{1}=1,fa_{2,..,n}=1)

( ext{for u = 2 to n do})
(v = fa_u)

求解(u,v)最小割

(w_u=lambda(u,v))

( ext{for x=u+1 to n do})

( ext{if} fa_x=v ext{ and x在u这一侧 then }fa_x=u)

( ext{end for})

( ext{end for})

但是这个东西实际也不会跑得比( ext{Gomory-Hu Tree})快,了解一下即可

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef double db;
typedef unsigned long long ull;
typedef pair <int,int> Pii;
#define reg register
#define pb push_back
#define mp make_pair
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
	T s=0; int f=0;
	while(!isdigit(IO=getchar())) if(IO=='-') f=1;
	do s=(s<<1)+(s<<3)+(IO^'0');
	while(isdigit(IO=getchar()));
	return f?-s:s;
}

const int N=860,M=170010,INF=1e9+10;

int n,m;
int U[M],V[M],W[M];

struct Edge{
	int to,nxt,w;
} e[M];
int head[N],ecnt;
void AddEdge(int u,int v,int w) {
	e[++ecnt]=(Edge){v,head[u],w};
	head[u]=ecnt;
}
void Link(int u,int v,int w){ AddEdge(u,v,w),AddEdge(v,u,w); }
#define erep(u,i) for(int i=head[u];i;i=e[i].nxt)
int dis[N],vc,S,T;
void clear(){ rep(i,1,vc) head[i]=0; ecnt=1,vc=0; }
int Bfs() {
	rep(i,1,vc) dis[i]=INF;
	static queue <int> que;
	dis[S]=0,que.push(S);
	while(!que.empty()) {
		int u=que.front(); que.pop();
		erep(u,i) {
			int v=e[i].to,w=e[i].w;
			if(!w || dis[v]<=dis[u]+1) continue;
			dis[v]=dis[u]+1,que.push(v);
		}
	}
	return dis[T]<INF;
}
int Dfs(int u,int in) {
	if(u==T) return in;
	int out=0;
	erep(u,i) {
		int v=e[i].to,w=e[i].w;
		if(!w || dis[v]!=dis[u]+1) continue;
		int t=Dfs(v,min(in-out,w));
		e[i].w-=t,e[i^1].w+=t,out+=t;
		if(in==out) break;
	}
	if(!out) dis[u]=0;
	return out;
}
int Dinic(){
	int ans=0;
	while(Bfs()) ans+=Dfs(S,INF);
	return ans;
}

int Mincut(int u,int v){
	clear(),vc=n,S=u,T=v;
	rep(i,1,m) Link(U[i],V[i],W[i]);
	return Dinic();
}

int fa[N],w[N];

int main() {
	n=rd(),m=rd();
	rep(i,1,m) U[i]=rd(),V[i]=rd(),W[i]=rd();
	rep(i,2,n) fa[i]=1;
	rep(u,2,n) {
		int v=fa[u]; w[u]=Mincut(v,u);
		rep(x,u+1,n) if(fa[x]==v && dis[x]==INF) fa[x]=u;
	}
}




原文地址:https://www.cnblogs.com/chasedeath/p/13553151.html