题目大意:给你一个有n个点的树,每个点都有其对应的颜色,给出m次询问(v,k),问v的子树中有多少种颜色至少出现k次
题解:先对所有的询问进行分类,即对所有相同的v合并到一起,这样就能转为离线处理(更新每个点的状态时同时求出答案)
开两个map<int,int>,cnt[i][j]表示 i 节点的子树中 j 颜色出现了多少次,f[i][j]则表示 i 节点的子树中至少出现 j 次的颜色个数。dfs的时候暴力枚举所有颜色合并能够得出正确答案,但这样做显然会TLE,因此需要用到启发式合并进行优化。即每次合并的时候是将小的集合并到大的集合中去,这样做的话就只需要枚举小集合里的元素,可以将合并的时间复杂度缩减到O(log n)的级别
#include<bits/stdc++.h> using namespace std; #define N 100001 int n,m,u,v,c[N],k[N],ans[N]; map<int,int>cnt[N],f[N]; vector<int>d[N],q[N]; void dfs(int cur,int pre) { cnt[cur][c[cur]]++,f[cur][1]++; for(auto nxt:d[cur])if(nxt!=pre) { dfs(nxt,cur); if(cnt[nxt].size()>cnt[cur].size()) cnt[cur].swap(cnt[nxt]),f[cur].swap(f[nxt]); for(auto x:cnt[nxt]) { int y=cnt[cur][x.first]; cnt[cur][x.first]+=x.second; for(int i=y+1;i<=y+x.second;i++)f[cur][i]++; } } for(auto i:q[cur])ans[i]=f[cur][k[i]]; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&c[i]); for(int i=2;i<=n;i++) { scanf("%d%d",&u,&v); d[u].push_back(v); d[v].push_back(u); } for(int i=1;i<=m;i++) scanf("%d%d",&v,&k[i]),q[v].push_back(i); dfs(1,0); for(int i=1;i<=m;i++) printf("%d ",ans[i]); }