Loading

圆方树

圆方树

前言

众所周知,树往往有着许多非常好的性质

圆方树就是一种把一个图变成一棵树的方式

定义

我们一般在无向图上使用圆方树

先介绍点双连通分量

一个点双连通图的定义:图中任意两点之间都至少有两条不同的路径可互相抵达

一个点双连通分量是一个极大点双连通图

在圆方树中,每个原图中的点对应一个圆点,每个点双对应方点,每个点(圆点)和其对应的点双(方点)连边

上图就分别展示了原图,点双以及对应的圆方树

显然,圆点不和圆点连,方点不和方点连

我们可以在方点处维护有关点双内多条路径的信息

如何建树

首先我们需要跑一遍 \(tarjan\) 找出每个点双,然后将每个点双代表的方点和点双内的点代表的圆点连边,就建出了一棵圆方树

如下:

inline void tarjan(int x){
	dfn[x]=low[x]=++cnt;
	S.push(x);
	for(auto y:G[x]){
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(low[y]==dfn[x]){
				++col_cnt;
				for(int j=0;j!=y;S.pop()){
					j=S.top();
					H[col_cnt].pb(j);
					H[j].pb(col_cnt);
				}
				H[col_cnt].pb(x);
				H[x].pb(col_cnt);
			}
		}
		else low[x]=min(low[x],dfn[y]);
	}
}

习题

CF487E tourists

题意

给定一张图(保证连通),每个点有点权。

现在有两种操作:

  • C a w:把 \(a\) 的点权改为 \(w\)

  • A a b:询问从 \(a\)\(b\) 的所有简单路径(不经过重复点)中,点权最小的点的点权。

思路

在图上搞树剖的操作?

考虑先把图变成一棵树

采用圆方树,然后搞树剖

容易想到把每个方点的权值设为周围所有圆点的最小的权值,搞个 \(multiset\) 维护一下

然而暴力修改会被卡

利用圆方树是一棵树的性质

我们把每个方点的权值设为其所有儿子节点的最小值,对于每一个方点开一个 \(multiset\) 维护最值

这样就可以保证复杂度

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

#define pb push_back
#define lsp p<<1
#define rsp p<<1|1
#define size pajo
#define int long long

const int N=3e5+5;
const int inf=1e9+5;

inline int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}

int n,m,q,cnt,col_cnt;
int w[N];
int low[N],dfn[N];
int dep[N],f[N],size[N],son[N];
int top[N],val[N];
stack <int> S;
multiset <int> s[N];
vector <int> G[N],H[N];

struct node{
	int l,r;
	int mn;
}t[N<<2];

node operator + (node l,node r){
	return (node){l.l,r.r,min(l.mn,r.mn)};
}

inline void build(int p,int l,int r){
	if(l==r){
		t[p]={l,r,val[l]};
		return;
	}
	int mid=l+r>>1;
	build(lsp,l,mid);
	build(rsp,mid+1,r);
	t[p]=t[lsp]+t[rsp];
}

inline void update(int p,int x,int k){
	if(t[p].l==t[p].r&&t[p].l==x){
		t[p].mn=k;
		return;
	}
	int mid=t[p].l+t[p].r>>1;
	if(x<=mid) update(lsp,x,k);
	else update(rsp,x,k);
	t[p]=t[lsp]+t[rsp];
}

inline node query(int p,int l,int r){
	if(l<=t[p].l&&t[p].r<=r) return t[p];
	node res={0,0,inf};
	int mid=t[p].l+t[p].r>>1;
	if(l<=mid) res=res+query(lsp,l,r);
	if(mid<r) res=res+query(rsp,l,r);
	return res;
}

inline void dfs1(int x,int fa){
	dep[x]=dep[fa]+1,size[x]=1,f[x]=fa;
	int maxson=-1;
	for(auto y:H[x]){
		if(y==fa) continue;
		dfs1(y,x);
		size[x]+=size[y];
		if(size[y]>maxson) maxson=size[y],son[x]=y;
	}
}

inline void dfs2(int x,int tp){
	dfn[x]=++cnt,top[x]=tp,val[cnt]=w[x];
	if(!son[x]) return;
	dfs2(son[x],tp);
	for(auto y:H[x]){
		if(y==f[x]||y==son[x]) continue;
		dfs2(y,y);
	}
}

inline int Q(int x,int y){
	node res=(node){0,0,inf};
	while(top[x]!=top[y]){
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res=res+query(1,dfn[top[x]],dfn[x]);
		x=f[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	res=res+query(1,dfn[x],dfn[y]);
	if(x>n)
		res.mn=min(res.mn,w[f[x]]);
	return res.mn;
}

inline void tarjan(int x){
	dfn[x]=low[x]=++cnt;
	S.push(x);
	for(auto y:G[x]){
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(low[y]==dfn[x]){
				++col_cnt;
				for(int j=0;j!=y;S.pop()){
					j=S.top();
					H[col_cnt].pb(j);
					H[j].pb(col_cnt);
				}
				H[col_cnt].pb(x);
				H[x].pb(col_cnt);
			}
		}
		else low[x]=min(low[x],dfn[y]);
	}
}

signed main(){
	n=read(),m=read(),q=read();
	col_cnt=n;
	for(int i=1;i<=n;++i) w[i]=read();
	for(int i=1;i<=m;++i){
		int x=read(),y=read();
		G[x].pb(y);
		G[y].pb(x);
	}
	tarjan(1);
	memset(dfn,0,sizeof(dfn));
	cnt=0;
	dfs1(1,0);
	for(int i=1;i<=n;++i)
		if(f[i])
			s[f[i]].insert(w[i]);
	for(int i=n+1;i<=col_cnt;++i)
		w[i]=*s[i].begin();
	dfs2(1,1);
	build(1,1,col_cnt);
	while(q--){
		char ch;
		cin>>ch;
		int x=read(),y=read();
		if(ch=='A') cout<<Q(x,y)<<endl;
		else{
			update(1,dfn[x],y);
			if(!f[x]){
				w[x]=y;
				continue;
			}
			s[f[x]].erase(s[f[x]].lower_bound(w[x]));
			s[f[x]].insert(y);
			if(w[f[x]]!=*s[f[x]].begin()){
				w[f[x]]=*s[f[x]].begin();
				update(1,dfn[f[x]],w[f[x]]);
			}
			w[x]=y;
		}
	}
}

P4606 战略游戏

题意

给出一个无向图,有 \(q\) 次询问:

每次给出一个点集 \(S\)\(2\le |S|\le n\)),问有多少个点 \(x\) 满足 \(x \notin S\) 且删掉 \(x\) 之后 \(S\) 中的点不全在一个连通分量中。

思路

先建出一棵圆方树,问题就转变成询问 \(S\) 在圆方树上对应的最小连通子图中的圆点个数减去 \(|S|\)

画图可知:

我们可以把 \(|S|\) 中的点按照 \(dfn\) 从小到大排序,计算排序后相邻两点之间链上圆点的个数之和

这样就可以覆盖整个连通子图

由于每个点被算了两次,最后答案要除以 \(2\)

如果子图中的深度最浅的节点 是圆点,答案还要加上 \(1\)

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

const int N=4e5+5;

#define pb push_back
#define re register

inline int read(){
	re int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}

int n,m,q;
int dfn[N],low[N],d[N],f[N][18],dep[N],s[N];
int cnt,tp,tot;
vector <int> G[N],H[N];

inline void tarjan(int x){
	dfn[x]=low[x]=++cnt;
	s[++tp]=x;
	for(re int y:G[x]){
		if(!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(low[y]==dfn[x]){
				++tot;
				for(re int j=0;j!=y;--tp){
					j=s[tp];
					H[tot].pb(j);
					H[j].pb(tot);
				}
				H[tot].pb(x);
				H[x].pb(tot);
			}
		} 
		else low[x]=min(low[x],dfn[y]);
	}
}

inline void dfs(int x,int fa){
	dfn[x]=++cnt;
	dep[x]=dep[fa]+1;
	f[x][0]=fa;
	d[x]=d[fa]+(x<=n);
	for(re int i=1;i<=17;++i)
		f[x][i]=f[f[x][i-1]][i-1];
	for(re int y:H[x])
		if(y!=fa) dfs(y,x);
}

inline int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(re int j=17;j>=0;--j)
		if(dep[f[x][j]]>=dep[y]) x=f[x][j];
	if(x==y) return x;
	for(re int j=17;j>=0;--j)
		if(f[x][j]!=f[y][j])
			x=f[x][j],y=f[y][j];
	return f[x][0]; 
}

inline void solve(){
	n=read(),m=read();
	for(re int i=1;i<=n;++i) G[i].clear(),dfn[i]=low[i]=0;
	for(re int i=1;i<=2*n;++i) H[i].clear();
	for(re int i=1;i<=m;++i){
		re int x=read(),y=read();
		G[x].pb(y);
		G[y].pb(x);
	}
	cnt=0,tot=n,tp=0;
	tarjan(1);
	cnt=0;
	dfs(1,0);
	q=read();
	while(q--){
		re int S,A[N];
		S=read();
		re int ans=-2*S;
		for(re int i=1;i<=S;++i) A[i]=read();
		sort(A+1,A+S+1,[](int i,int j){return dfn[i]<dfn[j];});
		for(re int i=1;i<=S;++i){
			re int x=A[i],y=A[i%S+1];
			ans+=d[x]+d[y]-2*d[lca(x,y)];
		}
		if(lca(A[1],A[S])<=n) ans+=2;
		printf("%d\n",ans/2);
	}
}

signed main(){
	re int T=read();
	while(T--)
		solve();
}
posted @ 2022-07-06 19:47  Into_qwq  阅读(58)  评论(0编辑  收藏  举报