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


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

    最小割树 ( 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;
    	}
    }
    
    
    
    
    
  • 相关阅读:
    MySQL表行数查询最佳实践
    mysqldump备份表中有大字段失败的排错过程
    μC/OSⅡ在C8051F060上的移植及其应用
    OSSchedLock()函数透析
    32位与64位区别
    【Cesium】Animation显示系统时间
    【Cesium】根据经纬度计算距离
    【Cesium】 鼠标拾取椭球、地形、模型坐标点(经度+纬度+高程)
    【Python学习】Selenium模拟浏览器抓取数据
    【Cesium】polygon中的height和extrudedHeight的区别
  • 原文地址:https://www.cnblogs.com/chasedeath/p/13553151.html
Copyright © 2020-2023  润新知