【Luogu P3233】[HNOI2014] 世界树

题目链接

题目描述

题意:
给你一颗树 , 每次询问一些关键点 , 一个点会被离他最近的编号最小的关键点控制,求出每次每个关键点控制的点数

Sol

这种每次询问一些点的题很可能就是虚树了

简单说一下虚树

要用虚树的题一般是因为保证了询问点的总数不会很多 , 如果每次询问都便利整棵树就太慢了,而虚树则是只建除了每次询问我们需要的点

首先虚树中的边不一定就仅仅只是一条边 , 可能是压缩的子树的一部分,可以理解为边上挂了很多棵子树 , 而虚树的 精髓&难点 就在怎么统计好这些被压缩了的子树的信息

如何构建?

我们不能够改变原树的结构 , 那么可以压缩的就是 dfs 过程中经过的没有用的点
而除了 要用到的关键点之外 , 他们之间的 LCA 也是我们需要保存的,这样才能保证树的结构是不变的

而其实总共的不同的 LCA 个数不超过 k-1 个,所以虚树上的点数显然是不超过 2k-1 的

而虚树的构建过程则是通过一个栈来维护一个从虚树的根节点到达当前最后一个节点的一条链

这样每次把一条链上的点顺次连边的话那么虚树显然就构建好了

维护的话要用到 dfs 序


放到这题,我们先建立好了虚树 , 那么问题就在于 , 考虑每个点应该被统计到哪个点上去

我们首先可以两遍 dfs 求出 每一个虚树上的点被哪个关键点控制
怎么求呢, 因为最近的关键点要么在子树内要么在子树外 , 那么我们先一遍 dfs 求出每个点子树内最近的关键点
第二边 dfs 用上面求出的最近关键点从上到下再更新一遍每个点最近的控制点就可以了

难点就在于怎么求出被压缩的子树的控制点了

我们只需要考虑一条虚树上的边情况,或者是没有被建出来的子树

没有被建出来的子树直接算到这棵子树的根节点的控制点上就可以了,因为虚树只保留了有用的点这条优美的性质保证了不会出错

那么对于边上的被压缩的子树呢?

对于虚树上的边 ( u , v ) (u,v) (u,v)
如果 u,v 被同一个点控制 , 那么这条边表示的一些子树就都是被该控制点控制,这个很显然

如果不是这样 , 那么控制他们的两个不同的点要从这个被压缩的子树中抢夺点

这种情况直接求出两个控制点的路径上的中点然后上下分别分配就可以了 , 这个点必定在 (u,v) 上,因为不在的话那么 u 和 v 就应当直接被较近的那个控制了

求中点的话就还要预处理一个倍增数组 ,那么这道题就做完了

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<queue>
#include<set>
#define Set(a,b) memset(a,b,sizeof(a))
using namespace std;
template <typename T> inline void init(T&x){
	x=0;char ch=getchar();bool t=0;
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') t=1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+(ch-48);
	if(t) x=-x;return;
}
const int N=3e5+10;
struct edge{int to,next;}a[N<<1];
int head[N],cnt=0;
int n,dep[N],id[N],I=0,fa[20][N],size[N];
inline void add(int x,int y){a[++cnt]=(edge){y,head[x]};head[x]=cnt;}
namespace LCA{
	int top[N],son[N];
	void dfs(int u,int ff){
		size[u]=1;id[u]=++I;
		for(int v,i=head[u];i;i=a[i].next){
			v=a[i].to;if(v==ff) continue;
			dep[v]=dep[u]+1;fa[0][v]=u;
			dfs(v,u);size[u]+=size[v];
			if((!son[u])||size[v]>size[son[u]]) son[u]=v;
		}
		return;
	}
	void Dfs(int u,int tp){
		top[u]=tp;if(!son[u]) return;
		Dfs(son[u],tp);
		for(int v,i=head[u];i;i=a[i].next) {
			v=a[i].to;if(v==fa[0][u]||v==son[u]) continue;
			Dfs(v,v);
		}
		return;
	}
	inline void Pre(){
		for(int k=1;k<=19;++k)
			for(int i=1;i<=n;++i)
				fa[k][i]=fa[k-1][fa[k-1][i]];
		return;
	}
	inline int QLCA(int u,int v) {while(top[u]^top[v]) {if(dep[top[u]]<dep[top[v]])swap(u,v);u=fa[0][top[u]];}return dep[u]<dep[v]? u:v;}
	void build(){dfs(1,0);Dfs(1,1);Set(head,0);cnt=0;Pre();return;}
	inline int Find(int u,int k){
		if(!k) return u;
		for(int h=0;k;k>>=1,++h) if(k&1) u=fa[h][u];
		return u;
	}
	inline int Dis(int u,int v){return dep[u]+dep[v]-(dep[QLCA(u,v)]<<1);}
}
using LCA::QLCA;
using LCA::Find;
using LCA::Dis;
namespace vitual_tree{
	int st[N];int top=0;int stk[N];bool is[N];int contr[N],ans[N],rt;
	inline bool cmp(int i,int j){return id[i]<id[j];}
	inline void insert(int u)
	{
		if(!top) {stk[++top]=u;return;}
		int lca=QLCA(stk[top],u);
		if(lca==stk[top]) return (void)(stk[++top]=u);
		while(top>1&&id[stk[top-1]]>=id[lca])
			add(stk[top-1],stk[top]),--top;
		if(lca!=stk[top]) add(lca,stk[top]),stk[top]=lca;
		stk[++top]=u;
		return;
	}
	void dfs1(int u){
		for(int v,i=head[u];i;i=a[i].next){
			v=a[i].to;dfs1(v);
			int p=contr[v];
			if(!contr[u]) contr[u]=p;
			else{
				int q=contr[u];
				if(dep[p]<dep[q]) contr[u]=p;
				else if(dep[p]==dep[q]) contr[u]=min(q,p);
			}
		}
		if(is[u]) contr[u]=u;return;
	}
	void dfs2(int u){
		int p=contr[u];
		for(int v,i=head[u];i;i=a[i].next) {
			v=a[i].to;
			int q=contr[v];
			int d1=Dis(p,v),d2=Dis(q,v);
			if(d1<d2) contr[v]=contr[u];
			else if(d1==d2) contr[v]=min(p,q);
			dfs2(v);
		}
		return;
	}
	void Dfs(int u){
		int sum=0;
		for(int v,i=head[u];i;i=a[i].next){
			v=a[i].to;
			int from=Find(v,dep[v]-dep[u]-1);
			if(contr[u]==contr[v]) ans[contr[u]]+=size[from]-size[v];
			else{
				int p=contr[u],q=contr[v];
				int D=Dis(p,q);
				int mid=Find(q,(D-1)>>1);
				if(D&1){
					ans[p]+=size[from]-size[mid];
					ans[q]+=size[mid]-size[v];
				}
				else{
					int F=fa[0][mid];
					ans[q]+=size[mid]-size[v];
					ans[p]+=size[from]-size[F];
					if(p<q) ans[p]+=size[F]-size[mid];
					else ans[q]+=size[F]-size[mid];
				}
			}
			sum+=size[from];
			Dfs(v);
		}
		ans[contr[u]]+=size[u]-sum;
		if(u!=rt) head[u]=0,contr[u]=0;
	}
	int tmp[N];
	void work() {
		int k;init(k);top=0;
		for(int i=1;i<=k;++i) init(st[i]),is[st[i]]=1,tmp[i]=st[i];
		sort(st+1,st+1+k,cmp);for(int i=1;i<=k;++i) insert(st[i]);
		rt=stk[1];while(top>1) add(stk[top-1],stk[top]),--top;
		dfs1(rt);dfs2(rt);
		Dfs(rt);cnt=0;
		if(rt!=1) ans[contr[rt]]+=n-size[rt];head[rt]=contr[rt]=0;
		for(int i=1;i<=k;++i) printf("%d ",ans[tmp[i]]),ans[tmp[i]]=0,is[tmp[i]]=0;
		puts("");
		return;
	}
	
}
int main()
{
	init(n);register int u,v;
	for(int i=1;i<n;++i){init(u);init(v);add(u,v);add(v,u);}
	LCA::build();int m;init(m);
	while(m--) vitual_tree::work();
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值