[Ynoi2011] 成都七中
一、题目
二、解法
建立原树的点分树,由于性质:对于树上任意一个连通块,存在一个连通块的点,使得整个连通块都在这个点的子树中。对于询问 \((l,r,x)\),暴力访问点分树上的祖先,可以找到最浅的和 \(x\) 在同一个连通块的点 \(p\),那么这个询问可以等效成 \((l,r,p)\),并且对询问有影响的点全部在 \(p\) 的子树内。
对于所有的 \(p\) 分别处理,考虑 \(p\) 的子树中,每个点 \(u\) 可以写成 \((mn,mx,col)\) 的三元组,分别代表 \(u\) 和 \(p\) 在原树路径上的最小编号 \(/\) 最大编号,和点 \(u\) 的颜色。那么问题变成了有多少种颜色满足 \(l\leq mn\leq mx\leq r\)
把询问和三元组一起离线下来,按照第一维从大到小排序。对于每种颜色,我们维护其最小的 \(mx\),显然把最小的 \(mx\) 放进树状数组中不重不漏,询问就可以直接查询了。
时间复杂度 \(O(n\log^2 n)\)
三、总结
本题最妙的一步其实是建立点分树。在本题的语境下,本质上还是要暴力枚举询问中的 \(x\) 这一维,但是点分树利用了其子树和为 \(O(n\log n)\) 的性质,优化了这一枚举的过程,这就是结构的魅力!类似的题目还有:Ridiculous Netizens
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 100005;
const int inf = 0x3f3f3f3f;
#define pb push_back
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,rt,sz,a[M],b[M],c[M],ans[M],siz[M],mx[M],vis[M];
struct node{int x,y,z,tp;};
vector<node> s[M],f[M];vector<int> g[M];
void find(int u,int fa)
{
siz[u]=1;mx[u]=0;
for(int v:g[u]) if(v^fa && !vis[v])
{
find(v,u);
siz[u]+=siz[v];
mx[u]=max(mx[u],siz[v]);
}
mx[u]=max(mx[u],sz-siz[u]);
if(mx[u]<mx[rt]) rt=u;
}
void dfs(int u,int fa,int mi,int mx)
{
mi=min(mi,u);mx=max(mx,u);
s[rt].pb({mi,mx,a[u],0});
f[u].pb({mi,mx,rt,0});
for(int v:g[u]) if(v^fa && !vis[v])
dfs(v,u,mi,mx);
}
void solve(int u)
{
vis[u]=1;dfs(u,0,inf,0);
for(int v:g[u]) if(!vis[v])
{
sz=siz[v];rt=0;
find(v,u);solve(rt);
}
}
void add(int x,int c)
{
for(int i=x;i<=n;i+=i&(-i)) b[i]+=c;
}
int ask(int x)
{
int r=0;
for(int i=x;i>0;i-=i&(-i)) r+=b[i];
return r;
}
void clear(int x)
{
for(int i=x;i<=n;i+=i&(-i)) b[i]=0;
}
signed main()
{
n=read();m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
g[u].pb(v);g[v].pb(u);
}
mx[0]=sz=n;find(1,0);solve(rt);
for(int i=1;i<=m;i++)
{
int l=read(),r=read(),x=read();
for(node t:f[x]) if(l<=t.x && t.y<=r)
{s[t.z].pb({l,r,i,1});break;}
}
memset(c,0x3f,sizeof c);
for(int i=1;i<=n;i++)
{
sort(s[i].begin(),s[i].end(),[&](node a,node b)
{return a.x>b.x || (a.x==b.x && a.tp<b.tp);});
for(node t:s[i])
{
if(t.tp) ans[t.z]=ask(t.y);
else if(t.y<c[t.z])
{
if(c[t.z]<inf) add(c[t.z],-1);
add(c[t.z]=t.y,1);
}
}
for(node t:s[i]) if(!t.tp)
c[t.z]=inf,clear(t.y);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
}