圆方树

圆方树相关知识

我们在做题的时候会发现:很多时候树比图好维护的多

例如求两点间路径长度,链上加法等

而在一些题中,应用圆方树能将图化为一棵树


圆方树最初用来处理仙人掌图,但是我们很多时候也能在一般无向图上使用

首先抛一个很大众的名词:点双连通图

点双连通图就是一个任意两点间都有至少两条除起点终点外,中间点不重复的路径的图

用兔队的话说一个近乎等价的定义就是不存在割点的图

为啥说是近乎等价呢?因为这个图:

它没有割点,但是不是点双联通

然后是另一个名词:点双联通分量

点双联通分量就是一个极大的点双联通子图

一个点可能属于多个点双,但是一条边恰好属于一个,或是不属于任何一个点双

我们把每个点双缩成一个方点,然后原来的点双上的点向方点连边

我们会发现原来的割点就是点双分割点,每个点双形成了一个星图

圆方树的构建

我们利用tarjan算法构建圆方树

fx(void,tarjan)(int now){
	low[now]=dfn[now]=++dfsc;
	stk[++top]=now;
//tarjan基本操作
	ann+=1;
//统计一个联通图中的点的个数
	for(R int i=head[now];i;i=e[i].na){
		if(!dfn[e[i].np]){
//是否访问过
			tarjan(e[i].np);
			low[now]=min(low[now],low[e[i].np]);
//追溯
			if(low[e[i].np]==dfn[now]){
				cnt+=1;
//方点+1
				do{
					add(sqh,sqe,snum,cnt,stk[top]);
					add(sqh,sqe,snum,stk[top],cnt);
					sqrn[cnt]+=1;
				} while(stk[top--]!=e[i].np);
				add(sqh,sqe,snum,cnt,now);
				add(sqh,sqe,snum,now,cnt);
//点双上的点向方点连边
			}
		} else low[now]=min(low[now],dfn[e[i].np]);
//访问过,更新
	}
}

圆方树例题

P4630 [APIO2018] Duathlon 铁人两项

题目链接

求互不相同的三元组 \(\langle s,c,f\rangle\) 的个数,其中 \(\langle s,c,f\rangle\) 表示存在一条简单路径 \(s\to f\),这条路径经过 \(c\)

有个显然的性质:给定一个点双中的点 \(a\),点双中的另外两个点 \(b,c\) 之间一定存在一条简单路径经过 \(a\)

那么所有路径并起来,就恰好完全等于这个点双

设方点集合为 \(S\),路径集合为 \(R\),设 \(s\in S,r\in R\) 那么 \(s\in r\) 当且仅当这条路径经过 \(s\)

本题就是求:

\[\sum\limits_{s\in S}\sum\limits_{r\in s}1 \]

设一个方点上面有 \(n\) 个子树,下面有 \(m\) 个子树,此方点做出的贡献就是

\[\sum\limits_{i=1}^n\sum\limits_{j=1}^m \text{size}_i\times \text{size}_j \]

利用乘法结合律即可 \(O(n)\) 求出

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 1000001
#define M 5001
#define INF 1100000000
#define Kafuu return
#define Chino 0
#define fx(l,n) inline l n
#define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
#define cpy(f,t,ty,len) memcpy(t,f,sizeof(ty)*len)
#define int long long
#define R register
#define C const
using namespace std;
int n,m,fr,to,head[N],sqh[N],num,snum,sqrn[N],cnt,dfsc,dfn[N],low[N],ann,top,stk[N],size[N],ans;
struct Edge{
	int na,np;
}e[N],sqe[N];
fx(void,add)(int *head,Edge *e,int &num,int f,int t){
	e[++num].na=head[f];
	e[num].np=t;
	head[f]=num;
}
fx(int,gi)(){
	R char c=getchar();R int s=0,f=1;
	while(c>'9'||c<'0'){
		if(c=='-') f=-f;
		c=getchar();
	}
	while(c<='9'&&c>='0') s=(s<<3)+(s<<1)+(c-'0'),c=getchar();
	return s*f;
}
fx(void,tarjan)(int now){
	low[now]=dfn[now]=++dfsc;
	stk[++top]=now;
	ann+=1;
	for(R int i=head[now];i;i=e[i].na){
		if(!dfn[e[i].np]){
			tarjan(e[i].np);
			low[now]=min(low[now],low[e[i].np]);
			if(low[e[i].np]==dfn[now]){
				cnt+=1;
				do{
					add(sqh,sqe,snum,cnt,stk[top]);
					add(sqh,sqe,snum,stk[top],cnt);
					sqrn[cnt]+=1;
				} while(stk[top--]!=e[i].np);
				add(sqh,sqe,snum,cnt,now);
				add(sqh,sqe,snum,now,cnt);
				sqrn[cnt]+=1;
			}
		} else low[now]=min(low[now],dfn[e[i].np]);
	}
}
fx(void,tdp)(int now,int fa){
	if(now<=n) size[now]=1;
	for(R int i=sqh[now];i;i=sqe[i].na){
		if(sqe[i].np==fa) continue;
		tdp(sqe[i].np,now);
		ans+=sqrn[now]*size[now]*size[sqe[i].np]*2;
		size[now]+=size[sqe[i].np];
	}
	ans+=sqrn[now]*size[now]*(ann-size[now])*2;
}
signed main(){
	n=gi(),m=gi();cnt=n;
	for(R int i=1;i<=n;i++) sqrn[i]=-1;
	for(R int i=1;i<=m;i++){
		fr=gi();to=gi();
		add(head,e,num,fr,to);
		add(head,e,num,to,fr);
	}
	for(R int i=1;i<=n;i++){
		if(!dfn[i]){
			ann=0;
			tarjan(i);
			top-=1;
			tdp(i,0);
		}
	}
	printf("%lld\n",ans);
}
posted @ 2021-03-07 11:56  OIer|zythonc  阅读(95)  评论(1编辑  收藏  举报