【模板】最小割树(Gomory-Hu Tree)-bzoj2229&4519

版权声明:欢迎转载(请附带原链接)ヾ(๑╹◡╹)ノ" https://blog.csdn.net/corsica6/article/details/84394427

模板传送门:洛谷【模板】最小割树
相关的题目都很板。。。


题意

给定一个 n n 个点 m m 条边的无向连通图,多次询问两点之间的最小割。


题解

某定理:
n n 个点的图上,两点之间只有 n 1 n-1 种本质不同的最小割。
所以存在一颗树,满足树上两点最小割等于原图上两点的最小割。
(不会证

最小割树的具体构造方法:
每次在点集中任选两个点分别作为源点和汇点跑最小割,在树中将这两个点连边,边权为最小割的值。将最小割划分出的两个互不相同的集合分别递归下去求解。

注意每次跑最小割都是在原图的基础上跑,所以要把每条边流量复原。

构造出树后,倍增回答即可。

还有另一种构造方法,这里有详细讲解。

原理比较显然,不再证明。


代码

#include<bits/stdc++.h>
using namespace std;
const int N=510,M=2e4+10,inf=0x3f3f3f3f;

int n,m,fe,S,T;
int head[N],cur[N],to[M],nxt[M],w[M],ori[M],tot=1;
int ht[N],dt[N<<1],nt[N<<1],wt[N<<1],cnt;
int dep[N],fa[N][15],dis[N][15],bin[20];
int rv[N],vs[N],rep[N],tim;

char cp,OS[100];
inline void rd(int &x)
{
	cp=getchar();x=0;
	for(;!isdigit(cp);cp=getchar());
	for(;isdigit(cp);cp=getchar()) x=(x<<3)+(x<<1)+(cp^48);
}

inline void ot(int x)
{
	int re=0;
	for(;(!re)||(x);x/=10) OS[++re]='0'+x%10;
	for(;re;--re) putchar(OS[re]);
	putchar('\n');
}

inline void lk(int u,int v,int vv)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=vv;ori[tot]=vv;}
inline void lkk(int u,int v,int vv)
{dt[++cnt]=v;nt[cnt]=ht[u];ht[u]=cnt;wt[cnt]=vv;}

queue<int>que;
inline bool bfs()
{
	memset(dep,0xff,sizeof(dep));
	dep[S]=1;que.push(S);
	int i,j,x;
	for(;que.size();){
		x=que.front();que.pop();
		for(i=head[x];i;i=nxt[i]){
			j=to[i];
			if((~dep[j])||(!w[i])) continue;
			dep[j]=dep[x]+1;que.push(j);
		}
	}
	return (dep[T]!=-1);
}

int dfs(int x,int f)
{
	if(x==T) return f;
	int j,ss=0,res;
	for(int &i=cur[x];i;i=nxt[i]){
		j=to[i];if(dep[j]!=dep[x]+1 || (!w[i])) continue;
		res=dfs(j,min(w[i],f-ss));if(!res) continue;
		w[i]-=res;w[i^1]+=res;ss+=res;if(ss==f) return ss;
	}
	if(!ss) dep[x]=-1;
	return ss;
}

void psh(int x)
{
	vs[x]=tim;
	for(int i=head[x];i;i=nxt[i]) 
	  if(w[i]&&(vs[to[i]]!=tim)) psh(to[i]);
}

inline void rn(int l,int r)
{
	if(l>=r) return;
	int res,i,ql=l,qr=r;fe=0;S=rv[l];T=rv[l+1];
	memcpy(w,ori,sizeof(int)*(tot+1));
	for(;bfs();){
		memcpy(cur,head,sizeof(cur));
		for(;;fe+=res)
		{res=dfs(S,inf);if(!res) break;};
	}
	lkk(S,T,fe);lkk(T,S,fe);tim++;psh(S);
	for(i=l;i<=r;++i) 
	  vs[rv[i]]==tim?rep[ql++]=rv[i]:rep[qr--]=rv[i];
	for(i=l;i<ql;++i) rv[i]=rep[i];
	for(i=qr+1;i<=r;++i) rv[i]=rep[i];
	rn(l,ql-1);rn(qr+1,r);
}

void df(int x)
{
	int i,j,k;
	for(i=1;bin[i]<=dep[x];++i){
		fa[x][i]=fa[fa[x][i-1]][i-1];
		dis[x][i]=min(dis[x][i-1],dis[fa[x][i-1]][i-1]);
	}
	for(i=ht[x];i;i=nt[i]){
		j=dt[i];if(j==fa[x][0]) continue;
		fa[j][0]=x;dis[j][0]=wt[i];
		dep[j]=dep[x]+1;df(j);
	}
}

int main(){
	int i,j,x,y,z,ans;
	memset(dis,0x3f,sizeof(dis));
	bin[0]=1;for(i=1;i<=15;++i) bin[i]=bin[i-1]<<1;
	rd(n);rd(m);
	for(i=1;i<=m;++i){
		rd(x);rd(y);rd(z);
		lk(x,y,z);lk(y,x,z);
	}
	for(i=1;i<=n;++i) rv[i]=i;
	rn(1,n);dep[1]=0;df(1);
	for(rd(m);m;--m){
		rd(x);rd(y);ans=inf;
		if(dep[x]<dep[y]) swap(x,y);
		z=dep[x]-dep[y];
		for(i=0;bin[i]<=z;++i) if(bin[i]&z){
			ans=min(ans,dis[x][i]);
			x=fa[x][i];
		}
		if(x!=y){
			for(i=9;~i;--i) if(fa[x][i]!=fa[y][i]){
				ans=min(ans,min(dis[x][i],dis[y][i]));
				x=fa[x][i];y=fa[y][i];
			}
			ans=min(ans,min(dis[x][0],dis[y][0]));
		}
		ot(ans);
	}
	return 0;
}

bzoj2229 zjoi2011最小割

构造出最小割树后 n 2 n^2 判断即可。

p.s.做这题的时候有个错误的思路:
并查集合并联通块,二分最大边权时的答案。然而合并连通块时两边的点之间的最小割不一定就是这条边的权值。

#include<bits/stdc++.h>
using namespace std;
const int N=200,M=2e4+10,inf=0x3f3f3f3f;

int n,m,fe,S,T,tk;
int head[N],cur[N],to[M],nxt[M],w[M],ori[M],tot;
int dep[N],val[N][N];
int rv[N],vs[N],rep[N],tim;

char cp,OS[100];
inline void rd(int &x)
{
	cp=getchar();x=0;
	for(;!isdigit(cp);cp=getchar());
	for(;isdigit(cp);cp=getchar()) x=(x<<3)+(x<<1)+(cp^48);
}

inline void ot(int x)
{
	int re=0;
	for(;(!re)||(x);x/=10) OS[++re]='0'+x%10;
	for(;re;--re) putchar(OS[re]);
	putchar('\n');
}

inline void lk(int u,int v,int vv)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=vv;ori[tot]=vv;}

queue<int>que;
inline bool bfs()
{
	memset(dep,0xff,sizeof(dep));
	dep[S]=1;que.push(S);
	int i,j,x;
	for(;que.size();){
		x=que.front();que.pop();
		for(i=head[x];i;i=nxt[i]){
			j=to[i];
			if((~dep[j])||(!w[i])) continue;
			dep[j]=dep[x]+1;que.push(j);
		}
	}
	return (dep[T]!=-1);
}

int dfs(int x,int f)
{
	if(x==T) return f;
	int j,ss=0,res;
	for(int &i=cur[x];i;i=nxt[i]){
		j=to[i];if(dep[j]!=dep[x]+1 || (!w[i])) continue;
		res=dfs(j,min(w[i],f-ss));if(!res) continue;
		w[i]-=res;w[i^1]+=res;ss+=res;if(ss==f) return ss;
	}
	if(!ss) dep[x]=-1;
	return ss;
}

void psh(int x)
{
	vs[x]=tim;
	for(int i=head[x];i;i=nxt[i]) 
	  if(w[i]&&(vs[to[i]]!=tim)) psh(to[i]);
}

inline void rn(int l,int r)
{
	if(l>=r) return;
	int res,i,j,ql=l,qr=r;fe=0;S=rv[l];T=rv[l+1];
	memcpy(w,ori,sizeof(int)*(tot+1));
	for(;bfs();){
		memcpy(cur,head,sizeof(cur));
		for(;;fe+=res)
		{res=dfs(S,inf);if(!res) break;};
	}
    tim++;psh(S);
    for(i=1;i<=n;++i)
     if(vs[i]==tim)
      for(j=1;j<=n;++j)
       if(vs[j]!=tim)
        val[i][j]=val[j][i]=min(val[i][j],fe);
	for(i=l;i<=r;++i) 
	  vs[rv[i]]==tim?rep[ql++]=rv[i]:rep[qr--]=rv[i];
	for(i=l;i<ql;++i) rv[i]=rep[i];
	for(i=qr+1;i<=r;++i) rv[i]=rep[i];
	rn(l,ql-1);rn(qr+1,r);
}

inline void sol()
{
	int i,j,x,y,z,ans;
	memset(val,0x3f,sizeof(val));
	memset(head,0,sizeof(head));tot=1;
	memset(vs,0,sizeof(vs));tim=0;
	rd(n);rd(m);
	for(i=1;i<=m;++i){
		rd(x);rd(y);rd(z);
		lk(x,y,z);lk(y,x,z);
	}
	for(i=1;i<=n;++i) rv[i]=i;rn(1,n);
	for(rd(m);m;--m){
		rd(x);ans=0;
		for(i=1;i<n;++i)
		 for(j=i+1;j<=n;++j)
		  if(val[i][j]<=x) ans++;
		ot(ans);
	}
}

int main(){
	for(rd(tk);tk;--tk) {sol();puts("");}
	return 0;
}

bzoj4519 [Cqoi2016]不同的最小割

同上,直接求。

#include<bits/stdc++.h>
using namespace std;
const int N=900,M=2e4+10,inf=0x3f3f3f3f;

int n,m,fe,S,T,tk;
int head[N],cur[N],to[M],nxt[M],w[M],ori[M],tot=1;
int dep[N],val[N][N],ans[N*N],cot;
int rv[N],vs[N],rep[N],tim;

char cp;
inline void rd(int &x)
{
	cp=getchar();x=0;
	for(;!isdigit(cp);cp=getchar());
	for(;isdigit(cp);cp=getchar()) x=(x<<3)+(x<<1)+(cp^48);
}

inline void lk(int u,int v,int vv)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;w[tot]=vv;ori[tot]=vv;}

queue<int>que;
inline bool bfs()
{
	memset(dep,0xff,sizeof(dep));
	dep[S]=1;que.push(S);
	int i,j,x;
	for(;que.size();){
		x=que.front();que.pop();
		for(i=head[x];i;i=nxt[i]){
			j=to[i];
			if((~dep[j])||(!w[i])) continue;
			dep[j]=dep[x]+1;que.push(j);
		}
	}
	return (dep[T]!=-1);
}

int dfs(int x,int f)
{
	if(x==T) return f;
	int j,ss=0,res;
	for(int &i=cur[x];i;i=nxt[i]){
		j=to[i];if(dep[j]!=dep[x]+1 || (!w[i])) continue;
		res=dfs(j,min(w[i],f-ss));if(!res) continue;
		w[i]-=res;w[i^1]+=res;ss+=res;if(ss==f) return ss;
	}
	if(!ss) dep[x]=-1;
	return ss;
}

void psh(int x)
{
	vs[x]=tim;
	for(int i=head[x];i;i=nxt[i]) 
	  if(w[i]&&(vs[to[i]]!=tim)) psh(to[i]);
}

inline void rn(int l,int r)
{
	if(l>=r) return;
	int res,i,j,ql=l,qr=r;fe=0;S=rv[l];T=rv[l+1];
	memcpy(w,ori,sizeof(int)*(tot+1));
	for(;bfs();){
		memcpy(cur,head,sizeof(cur));
		for(;;fe+=res)
		{res=dfs(S,inf);if(!res) break;};
	}
    tim++;psh(S);
    for(i=1;i<=n;++i)
     if(vs[i]==tim)
      for(j=1;j<=n;++j)
       if(vs[j]!=tim)
        val[i][j]=val[j][i]=min(val[i][j],fe);
	for(i=l;i<=r;++i) 
	  vs[rv[i]]==tim?rep[ql++]=rv[i]:rep[qr--]=rv[i];
	for(i=l;i<ql;++i) rv[i]=rep[i];
	for(i=qr+1;i<=r;++i) rv[i]=rep[i];
	rn(l,ql-1);rn(qr+1,r);
}

int main(){
	memset(val,0x3f,sizeof(val));
    int i,j,x,y,z;
	rd(n);rd(m);
	for(i=1;i<=m;++i){
		rd(x);rd(y);rd(z);
		lk(x,y,z);lk(y,x,z);
	}
	for(i=1;i<=n;++i) rv[i]=i;rn(1,n);
	for(i=1;i<n;++i)
	 for(j=i+1;j<=n;++j) ans[cot++]=val[i][j];
	sort(ans,ans+cot);
	cot=unique(ans,ans+cot)-ans;
	printf("%d\n",cot);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/corsica6/article/details/84394427