题解 CF375D 【Tree and Queries】

· · 题解

题解里貌似写的都是 DSU on tree 或者莫队的做法,这里介绍一个线段树合并,时间复杂度为 O(n \log n + m \log n) 的做法

对于 u 节点的子树,维护两颗线段树 T1_u,T2_uT1_u 下标 i 对应的值表示颜色为 i 的点在子树 u 内的个数,T2_u 下标 i 对应的值表示有多少颜色出现次数为 i

现在考虑如何从 u 的子节点 v_1,v_2...v_k 转移到 u

T1_u$ 的值实际上就是将所有 $T1_{v_1},T1_{v_2}...T1_{v_k}$ 进行线段树合并后,再在 $c_u$ 的位置加上 $1 对于一种颜色 $col$,我们可以进行两类分类 - 在子树 $u$ 中,颜色为 $col$ 的点都属于 $u$ 的一个儿子 $v$ 的子树 对于这类情况,在子树 $u$ 中 $col$ 的出现次数与在子树 $v$ 中是相同的,对于这类情况,实际上我们可以对于 $T2$ 也进行线段树合并 - 在子树 $u$ 中,颜色为 $col$ 的点属于 $u$ 的多个儿子 $v_1,v_2..v_k$ 的子树 对于这类情况,我们就只能暴力修改 $T2$ 了 那么实际上现在的问题就是如何快速找出哪些颜色是情况一,哪些颜色是情况二 实际上我们可以在 $T1$ 进行线段树合并时,找到所有被合并的叶子节点并记录它们对应的颜色,这样就找出了所有的情况二 由线段树合并的复杂度分析我们可以知道,情况二最多只会出现 $O(n)$ 次,所以暴力修改的复杂度是对的 那么该如何只对所有情况一的颜色进行线段树合并呢? 答案是把所有情况都装作是情况一,然后对于情况二的颜色,删去各个子树 $v$ 内颜色的出现次数,加入子树 $u$ 内颜色的出现次数 这里可能有一点细节问题,就是如何求出所有含有这种颜色的子树 $v$ 内的出现次数 也是在线段树合并到叶节点时特判一下,放到一个 `vector` 里 但是这些部分我们都没有讨论 $u$ 节点的颜色该如何处理 其实也比较简单,只要把 $u$ 节点的颜色当作是它自己一个特殊的子节点的颜色就行了 对于查询,其实就是在 $T2$ 上进行一下区间求和,如果追求在线算法的话可以选择可持久化线段树合并,但是我懒得写了,就写了离线算法 code: ```cpp #include <cstdio> #include <vector> using namespace std; int n,m; int a[100005]; int cnt; int head[100005]; struct eg{ int to,nxt; }edge[200005]; void make(int u,int v){ edge[++cnt].to = v; edge[cnt].nxt = head[u]; head[u] = cnt; } int T1[100005],T2[100005]; struct node{ int val,l,r; }tree[5000005]; int tag[100005]; #define ls(x) tree[x].l #define rs(x) tree[x].r void fx(int &x){ if(x == 0){ x = ++cnt; tree[x].val = ls(x) = rs(x) = 0; } } vector <int> S,T; void upload(int rt,int l,int r,int id,int C){ tree[rt].val += C; if(l == r) return; int mid = l + r >> 1; if(id <= mid){ fx(ls(rt)); upload(ls(rt),l,mid,id,C); }else{ fx(rs(rt)); upload(rs(rt),mid+1,r,id,C); } } int query(int rt,int l,int r,int L,int R){ if(!rt) return 0; if(l == L && r == R) return tree[rt].val; int mid = l + r >> 1; if(R <= mid) return query(ls(rt),l,mid,L,R); else if(L > mid) return query(rs(rt),mid+1,r,L,R); else return query(ls(rt),l,mid,L,mid) + query(rs(rt),mid+1,r,mid+1,R); } int Merge(int x,int y,int l,int r){ if(!x || !y) return x + y; if(l == r){ if(!tag[l] && tree[x].val > 0){ tag[l] = 1; S.push_back(l); T.push_back(tree[x].val); } if(tree[y].val > 0) T.push_back(tree[y].val); } tree[x].val += tree[y].val; int mid = l + r >> 1; ls(x) = Merge(ls(x),ls(y),l,mid); rs(x) = Merge(rs(x),rs(y),mid+1,r); return x; } int res[100005]; struct ask{ int k,id; }; vector <ask> Q[100005]; void dfs(int now,int fa){ for(int i = head[now];i;i = edge[i].nxt){ if(edge[i].to == fa) continue; dfs(edge[i].to,now); } S.clear(); T.clear(); fx(T1[now]); upload(T1[now],1,100000,a[now],1); for(int i = head[now];i;i = edge[i].nxt){ if(edge[i].to == fa) continue; T1[now] = Merge(T1[now],T1[edge[i].to],1,100000); } fx(T2[now]); upload(T2[now],1,100000,1,1); for(int i = 0;i < T.size();i++) upload(T2[now],1,100000,T[i],-1); for(int i = 0;i < S.size();i++) upload(T2[now],1,100000,query(T1[now],1,100000,S[i],S[i]),1); for(int i = head[now];i;i = edge[i].nxt){ if(edge[i].to == fa) continue; T2[now] = Merge(T2[now],T2[edge[i].to],1,100000); } for(int i = 0;i < S.size();i++) tag[S[i]] = 0; for(int i = 0;i < Q[now].size();i++) res[Q[now][i].id] = query(T2[now],1,100000,Q[now][i].k,100000); } int main(){ scanf("%d%d",&n,&m); for(int i = 1;i <= n;i++) scanf("%d",&a[i]); for(int i = 1;i < n;i++){ int u,v; scanf("%d%d",&u,&v); make(u,v);make(v,u); } for(int i = 1;i <= m;i++){ int u,k; scanf("%d%d",&u,&k); Q[u].push_back({k,i}); } cnt = 0; dfs(1,0); for(int i = 1;i <= m;i++) printf("%d\n",res[i]); return 0; } ```