P5311 [Ynoi2011] 成都七中

P5311 [Ynoi2011] 成都七中

题意

给你一棵 n 个节点的树,每个节点有一种颜色,有 m 次查询操作。

查询操作给定参数 l r x,需输出:

将树中编号在 [l,r] 内的所有节点保留,x 所在连通块中颜色种类数。

每次查询操作独立。

思路

考虑点分树的思想。假设我们已经建出点分树,对于每一个分治中心,我们应该维护什么东西?

我们从分治中心开始遍历,记录遍历到每个点路径上编号的最小值和最大值。很显然,如果遍历到一个询问的 x 的时候路径上最值范围在询问区间以内,那么对于这个询问 x 点与分治中心是联通的。

考虑这个分治中心是怎么给询问更新答案的。我们在遍历的时候记录一下到达每个点时的路径最值和该点的颜色,最后统计答案的时候枚举每个满足上述条件的询问的右端点,用"HH的项链"一题的方法用树状数组记录每种颜色的最小编号的最大值的颜色个数,查询区间答案即可。显然,这个时候树状数组里的每一个答案都与分治中心联通,而分治中心又与询问点联通,故能将范围内的答案统计完全。

那么问题来了,每个询问答案被统计几次、在哪里被统计能统计完全呢?

考虑点分树的结构。对于一个询问的编号区间,假设 u 为询问点 x 的点分树祖先,vu 点分树祖先,且两者都与 x 联通,那么 v 包含的范围一定比 u 大而且完全包含 u 的范围。我们刚才说在分治中心的统计能将整个范围内的答案都统计完全,所以 xv 处被统计一定包含在 u 处统计的所有答案。

我们再考虑一个事情,若 u v 定义同上,但ux 联通而 v 不与 x 联通,那么与 u 相对的 v 的彼处的所有点都一定不与 x 联通,因为这些点一定会经过 v 点。

综上,对于每个点,我们只需要选择一个深度最小的联通的祖先统计答案即可。这时统计的答案是完全的。

然后我们发现我们甚至不需要建出点分树。直接点分治,在每个分治中心搜索出范围内合法的询问,直接按照上述统计方式将询问答案求出,以后都不再更新该询问答案即可。

时间复杂度 O(nlog2n),空间复杂度 O(n)。空间小、时间常数小、代码短的方法谁不喜欢呢~

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cctype>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
inline int read(){
	int w=0,x=0;char c=getchar();
	while(!isdigit(c))w|=c=='-',c=getchar();
	while(isdigit(c))x=x*10+(c^48),c=getchar();
	return w?-x:x;
}
namespace star
{
	const int maxn=1e5+10;
	int n,m,c[maxn],C[maxn],ans[maxn],siz[maxn],rt,mx[maxn],lst[maxn];
	bool vis[maxn],mark[maxn];
	struct que{
		int l,r,id;
		que(){}
		que(int l,int r,int id):l(l),r(r),id(id){}
		bool operator < (const que &b) const {return r<b.r;}
	};
	vector<int> V[maxn];
	vector<que> qu[maxn],a,q;
	inline void insert(int x,int k){if(x)for(;x<=n;x+=x&-x) C[x]+=k;}
	inline int query(int x){int ans=0;for(;x;x-=x&-x) ans+=C[x]; return ans;}
	void getrt(int x,int fa,int S){
		siz[x]=1,mx[x]=0;
		for(auto u:V[x]) if(!vis[u] and u!=fa) getrt(u,x,S),mx[x]=max(mx[x],siz[u]),siz[x]+=siz[u];
		if((mx[x]=max(mx[x],S-siz[x]))<mx[rt]) rt=x;
	}
	void dfs(int x,int fa,int l,int r){
		siz[x]=1,a.emplace_back(l,r,c[x]);
		for(auto u:qu[x]) if(!mark[u.id] and u.l<=l and r<=u.r) mark[u.id]=true,q.push_back(u);
		for(auto u:V[x]) if(!vis[u] and u!=fa) dfs(u,x,min(l,u),max(r,u)),siz[x]+=siz[u];
	}
	void solve(int x){
		vis[x]=true;
		a.clear(),q.clear(),dfs(x,0,x,x);
		sort(a.begin(),a.end()),sort(q.begin(),q.end());
		for(int i=0,j=0;i<q.size();i++){
			while(j<a.size() and a[j].r<=q[i].r){
				if(a[j].l>lst[a[j].id]) insert(lst[a[j].id],-1),insert(lst[a[j].id]=a[j].l,1);
				++j;
			}
			ans[q[i].id]=query(n)-query(q[i].l-1);
		}
		for(auto u:a) insert(lst[u.id],-1),lst[u.id]=0;
		for(auto u:V[x]) if(!vis[u]) rt=0,getrt(u,x,siz[u]),solve(rt);
	}
	inline void work(){
		n=read(),m=read(),mx[0]=n+1;
		for(int i=1;i<=n;i++) c[i]=read();
		for(int u,v,i=1;i<n;i++) u=read(),v=read(),V[u].push_back(v),V[v].push_back(u);
		for(int l,r,i=1;i<=m;i++) l=read(),r=read(),qu[read()].emplace_back(l,r,i);
		getrt(1,0,n),solve(rt);
		for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	}
}
signed main(){
	star::work();
	return 0;
}
posted @   Star_Cried  阅读(95)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示