简单的圆方树(一)

圆方树是什么呢?


我们先不管仙人掌,来看看一般图的圆方树。
众所周知, T a r j a n Tarjan Tarjan算法可以在强连通分量,点双和边双三个地方使用。
有向图是强联通,无向图是双联通。
我们使用 T a r j a n Tarjan Tarjan算法很多时候都是为了将图简化以达到目的。
S c c Scc Scc缩点之后会形成一张 D A G DAG DAG图,用 T o p   S o r t Top\ Sort Top Sort可以解决许多问题。
边双缩点之后就直接形成了一棵树。
只有点双与众不同,点双中的割点是属于多个块的,所以emmm~~~
接下来就要用到圆方树了!


似乎多数圆方树有关的讲解都有这张图
(借了一张图,读书人的事……2333)
简单来说,就是对原图的每一个点双分量新建一个方点,然后将原有分量中的点连向它。
这样就避免了割点归属的争论了,完美的表现了割点属于多个块的性质(搁置争议共同开发)。
emm,说完了。

1.无论取哪个点为根,圆方树的形态是一样的
2.一定是个森林
3.每个点双有唯一的方点
4.圆点方点相间分布,相同点不相邻

具体的性质各位可以参考一下UOJ
接下来就是简单的例题时间了。


【BJOI2013】压力
Description
如今,路由器和交换机构建起了互联网的骨架。处在互联网的骨干位置的核心路由器典型的要处理100Gbit/s的网络流量。他们每天都生活在巨大的压力之下。
小强建立了一个模型。这世界上有N个网络设备,他们之间有M个双向的链接。这个世界是连通的。在一段时间里,有Q个数据包要从一个网络设备发送到另一个网络设备。
一个网络设备承受的压力有多大呢?很显然,这取决于Q个数据包各自走的路径。不过,某些数据包无论走什么路径都不可避免的要通过某些网络设备。
你要计算:对每个网络设备,必须通过(包括起点、终点)他的数据包有多少个?
Input
第一行包含3个由空格隔开的正整数N,M,Q。
接下来M行,每行两个整数u,v,表示第u个网络设备(从1开始编号)和第v个网络设备之间有一个链接。u不会等于v。两个网络设备之间可能有多个链接。
接下来Q行,每行两个整数p,q,表示第p个网络设备向第q个网络设备发送了一个数据包。p不会等于q。
Output
输出N行,每行1个整数,表示必须通过某个网络设备的数据包的数量。
Sample Input
4 4 2
1 2
1 3
2 3
1 4
4 2
4 3
Sample Output
2
1
1
2
Hint
【数据范围与约定】
样例解释:
设备1、2、3之间两两有链接,4只和1有链接。4想向2和3各发送一个数据包。显然,这两个数据包必须要经过它的起点、终点和1。
数据规模:
对于40%的数据,1<=N,M,Q<=2000
对于60%的数据,1<=N,M,Q<=40000
对于100%的数据,1<=N<=100000,1<=M,Q<=200000

我们知道,在一个点双联通分量中,任意两点之间都有点不重复的路径。
所以之间建出圆方树,然后两点之间的链上圆点(也就是割点)就是一定会经过的点。
树上差分即可。
有趣的是,我的差分方式有点毒瘤,仿照树剖得出Dfs序之后在序列上差分。
这样就没有求Lca的过程了,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=200005;
inline int read() {
	char c; int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n,m,Q;
struct Branch {int next,to;} branch[Maxn<<1];
int h[Maxn],cnt=0;
inline void add(int x,int y) {
	branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
}
Branch edge[Maxn<<2];
int e[Maxn<<1],idx=0;
inline void Add(int x,int y) {
	edge[++idx].to=y; edge[idx].next=e[x]; e[x]=idx; return ;
}
int Bcc,bl[Maxn<<1];
int dfn[Maxn<<1],low[Maxn<<1],ind=0;
int S[Maxn<<1],Top=0;
void Tarjan(int v) {
	dfn[v]=low[v]=++ind; S[++Top]=v;
	for(int i=h[v];i;i=branch[i].next) {
		int j=branch[i].to;
		if(!dfn[j]) {
			Tarjan(j); low[v]=min(low[v],low[j]);
			if(low[j]>=dfn[v]) {
				int p=0; ++Bcc; 
				Add(Bcc,v); Add(v,Bcc);
				while(p!=j) {
					p=S[Top--];
					Add(Bcc,p); Add(p,Bcc);
				}
			}
		}
		else low[v]=min(low[v],dfn[j]);
	} return ;
}
int size[Maxn<<1],deep[Maxn<<1];
int top[Maxn<<1],fa[Maxn<<1],son[Maxn<<1];
int id[Maxn],tnt=0;
void Dfs1(int v,int pre,int dep) {
	size[v]=1; fa[v]=pre; deep[v]=dep;
	for(int i=e[v];i;i=edge[i].next) {
		int j=edge[i].to;
		if(j==pre) continue;
		Dfs1(j,v,dep+1); size[v]+=size[j];
		if(size[son[v]]<size[j]) son[v]=j;
	} return ;
}
void Dfs2(int v,int T) {
	top[v]=T; id[v]=++tnt;
	if(son[v]) Dfs2(son[v],T);
	for(int i=e[v];i;i=edge[i].next) {
		int j=edge[i].to;
		if(j!=fa[v]&&j!=son[v]) Dfs2(j,j);
	} return ;
}
int sum[Maxn<<1];
int main() {
	n=read(); m=read(); Q=read();
	for(int i=1;i<=m;++i) {
		int x=read(),y=read();
		add(x,y); add(y,x);
	}
	Bcc=n; Tarjan(1);
	Dfs1(1,0,1); Dfs2(1,1);
	for(int i=1;i<=Q;++i) {
		int x=read(),y=read();
		while(top[x]!=top[y]) {
			if(deep[top[x]]<deep[top[y]]) swap(x,y);
			++sum[id[top[x]]]; --sum[id[x]+1];
			x=fa[top[x]];
		}
		if(deep[x]<deep[y]) swap(x,y);
		++sum[id[y]]; --sum[id[x]+1];
	}
	for(int i=1;i<=Bcc;++i) sum[i]+=sum[i-1];
	for(int i=1;i<=n;++i) cout<<sum[id[i]]<<'\n';
	return 0;
}

圆方树显然可以解决一些带修改的问题。
这里我们要注意一下圆方树的维护。
显然,如果没修改一次圆点就修改所有与之相连的方点的话,在菊花图面前会死的很惨。
于是我们随便拿一个圆点当作圆方树的根,所有的方点维护的信息都不包括父亲节点(当然是一个圆点)。 当我们访问到当前联通块时特判后加上父亲圆点的贡献即可。
题目链接
题目大意:给出一张无向图,然后每次询问两点之间简单路径的最小值,支持修改点权。
首先按照前面的说法建好圆方树,然后就是维护链上的最小值。同时,用multiset来维护方点的最小值(相当于一个支持删除的小根堆)。配合树链剖分+线段树就解决问题了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<set>
using namespace std;
const int Maxn=200005;
inline int read() {
   char c; int rec=0;
   while((c=getchar())<'0'||c>'9');
   while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
   return rec;
}
int n,m,Q,val[Maxn];
struct Graph {
   struct Branch {int next,to;} branch[Maxn<<1];
   int h[Maxn],cnt;
   inline void add(int x,int y) {
   	branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
   }
} G,T;
int Bcc,dfn[Maxn],low[Maxn],idx,S[Maxn],Top;
void Tarjan(int v) {
   dfn[v]=low[v]=++idx; S[++Top]=v;
   for(int i=G.h[v];i;i=G.branch[i].next) {
   	int j=G.branch[i].to;
   	if(!dfn[j]) {
   		Tarjan(j); low[v]=min(low[v],low[j]);
   		if(low[j]>=dfn[v]) {
   			int p=0; ++Bcc; 
   			T.add(Bcc,v); T.add(v,Bcc);
   			while(p!=j) {
   				p=S[Top--];
   				T.add(Bcc,p); T.add(p,Bcc);
   			}
   		}
   	}
   	else low[v]=min(low[v],dfn[j]);
   } return ;
}
multiset<int> U[Maxn<<1];
int D,Minn[Maxn<<3];
int size[Maxn<<1],deep[Maxn<<1];
int top[Maxn<<1],fa[Maxn<<1],son[Maxn<<1];
int id[Maxn<<1],ind;
void Dfs1(int v,int pre,int dep) {
   if(v<=n) U[v].insert(val[v]);
   size[v]=1; fa[v]=pre; deep[v]=dep;
   for(int i=T.h[v];i;i=T.branch[i].next) {
   	int j=T.branch[i].to;
   	if(j==pre) continue;
   	Dfs1(j,v,dep+1); size[v]+=size[j];
   	if(size[son[v]]<size[j]) son[v]=j;
   	if(v>n) U[v].insert(val[j]);
   } return ;
}
void Dfs2(int v,int TOP) {
   top[v]=TOP; id[v]=++ind;
   if(son[v]) Dfs2(son[v],TOP);
   for(int i=T.h[v];i;i=T.branch[i].next) {
   	int j=T.branch[i].to;
   	if(j!=fa[v]&&j!=son[v]) Dfs2(j,j);
   } return ;
}
inline int Ask(int L,int R) {
   int rec=0x3f3f3f3f;
   for(int s=L+D-1,t=R+D+1;s^t^1;s>>=1,t>>=1) {
   	if(~s&1) rec=min(rec,Minn[s^1]);
   	if(t&1) rec=min(rec,Minn[t^1]);
   } return rec;
}
int main() {
   n=read(); m=read(); Q=read();
   for(int i=1;i<=n;++i) val[i]=read();
   for(int i=1;i<=m;++i) {
   	int x=read(),y=read();
   	G.add(x,y); G.add(y,x);
   }
   char ch;
   Bcc=n; Tarjan(1);
   Dfs1(1,0,1); Dfs2(1,1);
   D=1; while(D<=Bcc) D<<=1;
   memset(Minn,0x3f,sizeof(Minn));
   for(int i=1;i<=Bcc;++i) Minn[D+id[i]]=*U[i].begin();
   for(int i=D;i;--i) Minn[i]=min(Minn[i<<1],Minn[i<<1|1]);
   for(int i=1;i<=Q;++i) {
   	while((ch=getchar())!='A'&&ch!='C');
   	int x=read(),y=read();
   	if(ch=='A') {
   		int Ans=0x3f3f3f3f;
   		while(top[x]!=top[y]) {
   			if(deep[top[x]]<deep[top[y]]) swap(x,y);
   			Ans=min(Ans,Ask(id[top[x]],id[x]));
   			x=fa[top[x]];
   		}
   		if(deep[x]<deep[y]) swap(x,y);
   		Ans=min(Ans,Ask(id[y],id[x]));
   		if(y>n) Ans=min(Ans,val[fa[y]]);
   		cout<<Ans<<'\n';
   	}
   	else {
   		int p=fa[x];
   		if(p) {
   			U[p].erase(U[p].find(val[x])); U[p].insert(y);
   			int v=id[p]+D; Minn[v]=*U[p].begin(); v>>=1;
   			while(v) Minn[v]=min(Minn[v<<1],Minn[v<<1|1]),v>>=1;
   		}
   		val[x]=y;
   		int v=id[x]+D; Minn[v]=y; v>>=1;
   		while(v) Minn[v]=min(Minn[v<<1],Minn[v<<1|1]),v>>=1;
   	}
   }
   return 0;
}

除了可以辅助数据结构来暴力维护图上信息之外,圆方树还可以将图上的 d p dp dp转化为树上的 d p dp dp
LOJ 【APIO2018】铁人两项
简单来说,就是找原图中有多少在同一简单路上的三元组。
一种统计方法是枚举s和f,然后计算从s到f的点不重复路径中可以经过的点的个数。
设每个圆点的权值为−1,每个方点的权值为点双大小,那么选法就是两点路径的权值和吧。
O ( n 2 ) O(n^2) O(n2)任然不行,所以我们考虑计算一下每个点的贡献。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=200005,Maxm=400005;
inline int read() {
	char c; int rec=0;
	while((c=getchar())<'0'||c>'9');
	while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
	return rec;
}
int n,m;
long long ans;
struct Graph {
	struct Branch {int next,to;} branch[Maxm<<1];
	int h[Maxn<<1],cnt;
	inline void add(int x,int y) {
		branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
	}
} G,T;
int size[Maxn<<1],tot;
int Bcc,dfn[Maxn],low[Maxn],S[Maxn],w[Maxn],idx,num;
void Tarjan(int v) {
	dfn[v]=low[v]=++idx; S[++num]=v; w[v]=-1;
	for(int i=G.h[v];i;i=G.branch[i].next) {
		int j=G.branch[i].to;
		if(!dfn[j]) {
			Tarjan(j); low[v]=min(low[v],low[j]);
			if(low[j]>=dfn[v]) {
				int p=0; ++Bcc; ++w[Bcc];
				T.add(Bcc,v); T.add(v,Bcc);
				while(p!=j)
					p=S[num--],++w[Bcc],T.add(Bcc,p),T.add(p,Bcc);
			}
		}
		else low[v]=min(low[v],dfn[j]);
	} return ;
}
void Getsize(int v,int pre) {
	size[v]=v<=n;
	for(int i=T.h[v];i;i=T.branch[i].next) {
		int j=T.branch[i].to;
		if(j==pre) continue;
		Getsize(j,v); size[v]+=size[j];
	} return ;
}
void Sov(int v,int pre) {
	int opt=v<=n;
	for(int i=T.h[v];i;i=T.branch[i].next) {
		int j=T.branch[i].to;
		if(j==pre) continue;
		Sov(j,v); ans+=1ll*opt*w[v]*size[j]; opt+=size[j];
	}
	ans+=1ll*size[v]*(tot-size[v])*w[v];
	return ;
}
int main() {
	n=read(); m=read();
	for(int i=1;i<=m;++i) {
		int x=read(),y=read();
		G.add(x,y); G.add(y,x);
	}
	Bcc=n;
	for(int i=1;i<=n;++i)
		if(!dfn[i]) 
			Tarjan(i),Getsize(i,0),tot=size[i],Sov(i,0);
	cout<<(ans<<1);
	return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值