zoukankan      html  css  js  c++  java
  • P5311 [Ynoi2011] 成都七中

    P5311 [Ynoi2011] 成都七中

    题意

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

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

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

    每次查询操作独立。

    思路

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

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

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

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

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

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

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

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

    时间复杂度 (O(nlog^2n)),空间复杂度 (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
    ",ans[i]);
    	}
    }
    signed main(){
    	star::work();
    	return 0;
    }
    
  • 相关阅读:
    MaxScript键盘控制盒子的移动
    MaxScript无需窗口的Timer
    Max用.Net的Dictionary将汉字转化为拼音
    关于技术美术的一些个人理解
    Max的工具部署与安装
    用以加强MaxScript的补充
    MaxScript 清除超出范围的关键帧
    Max2008之前版本旋转视图的函数
    面向对象设计的原则迪米特原则(转)
    LDAP资料
  • 原文地址:https://www.cnblogs.com/BrotherHood/p/14618239.html
Copyright © 2011-2022 走看看