可以离线,子树查询,数据范围是1e5。
全局维护num[i]表示颜色i出现的次数,cnt[i]表示出现次数大于等于i次的颜色种类数。
由于num[i]每次都是加1,减1,变化是连续的,所以cnt数组的维护只需要单点修改:
当新加入颜色x,num[x]+1这个数增加1个,num[x]这个数减少1个,
所以cnt[num[x]+1]+1
,而cnt[num[x]]
不变。
同理,当减去颜色x,cnt[num[x]]-1
。
代码略。
如果考虑用树上启发式,我们同样维护num[i],cnt[i],含义一样。
当然如果用cnt[i]表示出现次数恰好等于i次的颜色种类数,那么每次查询都是区间查询,需要对cnt这个桶,建线段树维护。
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
#define pdd pair<double, double>
#define re register
#define lc rt<<1
#define rc rt<<1|1
const int maxn = 1e5 + 10;
const ll mod = 998244353;
const ll inf = (ll)4e17+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){
if(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
int ret,num[maxn];//num[i] 颜色i的节点数
int cnt[maxn];//节点数大于等于i的颜色种类数 u加进去 num[u]++ cnt[num[u]]++ 查询就cnt[k]
int dep[maxn],son[maxn],siz[maxn];
int in[maxn],clk,pos[maxn];
vector<int> g[maxn];
int c[maxn];
vector<pii> q[maxn];
int n,m;
int ans[maxn];
void dfs(int rt,int fa)
{
dep[rt]=dep[fa]+1;
in[rt]=++clk;
pos[clk]=rt;
siz[rt]=1;
for(int i:g[rt])
{
if(i==fa) continue;
dfs(i,rt);
siz[rt]+=siz[i];
if(siz[i] > siz[son[rt]]) son[rt]=i;
}
}
int SON;
void add(int rt,int v)
{
for(int i=in[rt];i<in[rt]+siz[rt];i++)
{
int u=pos[i];
if(u==SON) i=i+siz[SON]-1;
else
{
if(v==1)
{
num[c[u]]++;
cnt[num[c[u]]]++;
}
else
{
num[c[u]]--;
cnt[num[c[u]]+1]--;
}
}
}
}
void dfs2(int rt,int fa,bool save)
{
for(int i:g[rt])
{
if(i==son[rt] || i==fa) continue;
dfs2(i,rt,0);
}
if(son[rt]) dfs2(son[rt],rt,1),SON=son[rt];
add(rt,1),SON=0;
for(auto it:q[rt])
{
ans[it.second]=cnt[it.first];
}
if(!save)
{
add(rt,-1);
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",c+i);
for(int i=2,u,v;i<=n;i++)
{
scanf("%d %d",&u,&v);
g[u].push_back(v),g[v].push_back(u);
}
dfs(1,0);
for(int i=1;i<=m;i++)
{
int x,k;
scanf("%d %d",&x,&k);
q[x].push_back({
k,i});
}
dfs2(1,0,1);
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}