375D.Tree and Queries(树上启发式合并+离线)
给出一个n个点的树。
每次询问点x的子树内出现次数为y的颜色至少有多少种。
题解:
先将询问离线。
用f数组记录每种颜色在当前子树内的出现次数情况
用cnt数组记录当前子树内出现次数大于等于i的不同颜色数量
当前节点的f和cnt来源于重儿子。
然后更新f的状态时,cnt[f[i]]++即可。
然后遍历与当前节点有关的每个询问,依次输出cnt[f[i]]即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+100;
int n,m;
int c[maxn];
vector<int> g[maxn];
int tot;
int L[maxn];
int R[maxn];
int id[maxn];
int son[maxn];
int f[maxn];
int ans[maxn];
int size[maxn];
int cnt[maxn];
int dep[maxn];
vector<pair<int,int> > q[maxn];
//first表示询问的编号,second表示询问的k
void dfs1 (int x,int pre) {
dep[x]=dep[pre]+1;
size[x]=1;
L[x]=++tot;
id[tot]=x;
for (int y:g[x]) {
if (y==pre) continue;
dfs1(y,x);
size[x]+=size[y];
if (size[son[x]]<size[y]) son[x]=y;
}
R[x]=tot;
}
void cal (int x,int pre) {
f[c[x]]++;
cnt[f[c[x]]]++;
for (int y:g[x]) {
if (y==son[x]||y==pre) continue;
for (int j=L[y];j<=R[y];j++) {
int z=id[j];
f[c[z]]++;
cnt[f[c[z]]]++;
}
}
for (pair<int,int> y:q[x]) {
ans[y.second]=cnt[y.first];
}
}
void dfs2 (int x,int pre,int kp) {
for (int y:g[x]) {
if (y==son[x]) continue;
if (y==pre) continue;
dfs2(y,x,0);
}
if (son[x]) {
dfs2(son[x],x,1);
}
cal(x,pre);
if (!kp) {
int Max=0;
for (int i=L[x];i<=R[x];i++) {
Max=max(Max,f[c[id[i]]]);
f[c[id[i]]]=0;
}
for (int i=0;i<=Max;i++) cnt[i]=0;
}
}
int main () {
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",c+i);
for (int i=1;i<n;i++) {
int x,y;
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
for (int i=1;i<=m;i++) {
int x,y;
scanf("%d%d",&x,&y);
q[x].push_back(make_pair(y,i));
}
dfs1(1,0);
dfs2(1,0,1);
for (int i=1;i<=m;i++) printf("%d\n",ans[i]);
}