题目大意
给定一棵树,n个节点,m个询问。
每次询问以
vi
为根的子树中,点数大于等于
ki
的颜色个数。
题解
对于这种询问子树的问题,经常会转到序列上。毕竟,序列较树有着无比优越性。
比如说,莫队算法。
利用dfs序将树转到序列上后,就可以套用莫队算法了。
维护区间时需要维护每种颜色的个数
cnti
,同时要维护颜色大于等于i的颜色个数
sumi
。
每次删除一个数,就把这种颜色的cnt减一,由于只有这种颜色变化了,所以sum变化的只有原来的
cntcoli
,所以在减一之前,先把
sumcntcoli
减一。
当加入一个数时,先把这个颜色的cnt加一,然后,
sumcntcoli
也要加一。
#include<cstdio>
#include<vector>
#include<cassert>
#include<cmath>
#include<cctype>
#include<set>
#include<algorithm>
using namespace std;
const int M=100005;
int col[M],L[M],R[M],dfs_clock,s;
int ans[M],cnt[M],sum[M],tmp[M];
vector<int>edge[M];
struct Query{
int L,R,num,id;
}q[M];
inline void rd(int&res){
res=0;char c;
while(c=getchar(),!isdigit(c));
do res=res*10+(c^48);
while(c=getchar(),isdigit(c));
}
void rec(int x,int f){
L[x]=++dfs_clock;
for(int i=0;i<edge[x].size();i++)
if(edge[x][i]!=f) rec(edge[x][i],x);
R[x]=dfs_clock;
}
bool cmp(Query a,Query b){
if(a.L/s==b.L/s) return a.R<b.R;
return a.L/s<b.L/s;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
s=(int)sqrt(n);
for(int i=1;i<=n;i++)
rd(col[i]);
for(int i=1;i<n;i++){
int a,b;
rd(a);rd(b);
edge[a].push_back(b);
edge[b].push_back(a);
}
rec(1,0);
for(int i=0;i<m;i++){
int a,b;
rd(a);rd(b);
q[i]=(Query){L[a],R[a],b,i};
}
for(int i=1;i<=n;i++)
tmp[i]=col[i];
for(int i=1;i<=n;i++)
col[L[i]]=tmp[i];
sort(q,q+m,cmp);
int L=0,R=0,res=0;
for(int i=0;i<m;i++){
while(R<q[i].R){
R++;
cnt[col[R]]++;
sum[cnt[col[R]]]++;
}
while(L>q[i].L){
L--;
cnt[col[L]]++;
sum[cnt[col[L]]]++;
}
while(L<q[i].L){
sum[cnt[col[L]]]--;
cnt[col[L]]--;
L++;
}
while(R>q[i].R){
sum[cnt[col[R]]]--;
cnt[col[R]]--;
R--;
}
ans[q[i].id]=sum[q[i].num];
}
for(int i=0;i<m;i++)
printf("%d\n",ans[i]);
return 0;
}
遗留问题
这题还可以用map的启发式合并来写。