l u o g u P 2664 树 上 游 戏 \tt luogu\ P2664 \ 树上游戏 luogu P2664 树上游戏
题目大意:
一棵树上每个节点都有颜色,给定一个长度为 n n n 的颜色序列,定义 s ( i , j ) s(i, j) s(i,j) 为 i → j i \to j i→j 的颜色数量。
设 s u m i = ∑ j = 1 n s ( i , j ) sum_i = \sum_{j = 1} ^ n s(i, j) sumi=∑j=1ns(i,j)。求所有的 s u m i sum_i sumi。
这个本质可以看成树上的点对的问题。既然不考虑在线我们可以使用离线算法点分治。
我们需要在 O ( n ) O(n) O(n) 或者 O ( n log n ) O(n \log n) O(nlogn) 的复杂度完成统计以当前节点为 l c a lca lca 的点对的所有贡献。
显然暴力统计是不行的。考虑借鉴一下统计树上距离为 k k k 的点对的方法,开桶进行计算。
首先考虑以根节点为起点的方案数,也就是考虑每一条链上第一个出现的节点,其对于根的贡献是其子树大小。
其子树中每一个点都会产生一次这样颜色的贡献。
为了方便统计我们将根节点的颜色也加上贡献,同时提前打上标记。
之后需要统计点对的贡献。我们不妨将上一次产生贡献的点的贡献记成 f ( i ) f(i) f(i)。那么不产生贡献显然是 f ( i ) = 0 f(i) = 0 f(i)=0。
既然是统计子树中的贡献。考虑遍历每一个子树。
对于每个点的贡献就是。设当前的颜色集合为 S S S。点集合是 T T T,子树的大小是 S i z Siz Siz。
∑ f − ∑ v ∈ T f ( v ) − ∑ v ∉ T , c o l v ∈ S f ( v ) + ∑ u ∈ S ( A l l − S i z ) \sum f - \sum_{v \in T} f(v) - \sum_{v \not\in T,col_v \in S} f(v) +\sum_{u \in S} (All - Siz) ∑f−v∈T∑f(v)−v∈T,colv∈S∑f(v)+u∈S∑(All−Siz)
后面的就是外面的点对当前子树的总贡献。
具体来说:对于一个点 v v v 可以变成 x → r t → v x \to rt \to v x→rt→v。显然直接统计会有重复的贡献。从 x → r t x \to rt x→rt 显然就是不在当前子树的 f f f 的贡献之和,也就是前面两项。然后这会产生重复的贡献,那么我们考虑换根,也就是最近的节点是当前遍历到的颜色和之前重复的节点,所以需要减去之前和当前颜色相同的节点的贡献,也就是第 3 3 3 项,最后一项就是从 r t → v rt \to v rt→v 的所有点的贡献次数。
我们实现的时候开桶记录颜色为 x x x 的 f f f 之和。每个子树的 f f f 之和。然后开桶记录之前出现的颜色进行叠加即可。
注意: 根到每个节点的贡献我们还没有计算,在 d f s dfs dfs 的时候顺便加入即可。
注意: 我们找到一个新的根的时候需要重新计算一下 s i z siz siz。
说句闲话,感觉我之前写的点分治虽然复杂度对,但是挺假的。
之后我们还需要清空节点,变成一遍 d f s dfs dfs 就好了。
#include <bits/stdc++.h>
using namespace std;
template <typename T>
void r1(T &x) {
x = 0;
char c(getchar());
int f(1);
for(; c < '0' || c > '9'; c = getchar()) if(c == '-') f = -1;
for(; '0' <= c && c <= '9';c = getchar()) x = (x * 10) + (c ^ 48);
x *= f;
}
template <typename T,typename... Args> inline void r1(T& t, Args&... args) {
r1(t); r1(args...);
}
//#define int long long
const int maxn = 2e5 + 5;
const int maxm = maxn << 1;
int head[maxn], cnt;
struct Edge {
int to, next;
}edg[maxn << 1];
void add(int u,int v) {
edg[++ cnt] = (Edge) { v, head[u] }, head[u] = cnt;
}
int siz[maxn], mx[maxn];
int vis[maxn];
int Mx(2e9), rt, All;
void getrt(int p,int pre) {
siz[p] = 1, mx[p] = 0;
for(int i = head[p];i;i = edg[i].next) {
int to = edg[i].to; if(to == pre || vis[to]) continue;
getrt(to, p);
siz[p] += siz[to];
mx[p] = max(mx[p], siz[to]);
}
mx[p] = max(All - siz[p], mx[p]);
if(mx[p] < Mx) Mx = mx[p], rt = p;
}
int col[maxn], a[maxn];
long long colf[maxn]; // colf[i] 全树中颜色为 i 的点的 f 贡献
long long branf[maxn];// branf[i] 当前子树中 f 之和
long long brancolf[maxn];// brancolf[i] 当前子树中颜色为 i 的 f 之和
int tcol[maxn], af, tot(0); // 开桶记录全部所有的颜色,方便清空
void dfs1(int p,int pre,int br) { // 重新计算子树大小
if(pre == rt) br = p;
siz[p] = 1;
int isn(0), cl(a[p]);
if(!col[cl]) isn = col[cl] = 1;
for(int i = head[p];i;i = edg[i].next) {
int to = edg[i].to; if(to == pre || vis[to]) continue;
dfs1(to, p, br);
siz[p] += siz[to];
}
if(isn) {
if(!colf[cl])
tcol[++ tot] = cl;
colf[cl] += siz[p];
branf[br] += siz[p]; // 记录子树中 f 的贡献
af += siz[p]; // 计算所有 f 的贡献
col[cl] = 0;
}
}
void dfs2(int p,int pre,int br) {
int cl(a[p]), isn(0);
if(!col[cl]) isn = col[cl] = 1;
for(int i = head[p];i;i = edg[i].next) {
int to = edg[i].to; if(to == pre || vis[to]) continue;
dfs2(to, p, br);
}
if(isn) col[cl] = 0, brancolf[cl] += siz[p];
}
long long ans[maxn];
void dfs3(int p,int pre,int br) {
ans[p] += All - siz[br];
int cl(a[p]), isn(0);
if(!col[cl]) {
isn = col[cl] = 1;
af -= (colf[cl] - brancolf[cl]);
af += (All - siz[br]);
}
ans[p] += af;
for(int i = head[p];i;i = edg[i].next) {
int to = edg[i].to; if(to == pre || vis[to]) continue;
dfs3(to, p, br);
}
if(isn) {
col[cl] = 0;
af += (colf[cl] - brancolf[cl]);
af -= (All - siz[br]);
}
}
void dfs4(int p,int pre) { // 清空当前子树的颜色的 f
brancolf[a[p]] = 0;
for(int i = head[p];i;i = edg[i].next) {
int to = edg[i].to; if(to == pre || vis[to]) continue;
dfs4(to, p);
}
}
int n;
void dfs(int p) {
vis[p] = 1;
af = 0;
tot = 0;
tcol[++ tot] = a[p];
col[a[p]] = 1;
af = 0;
dfs1(p, 0, 0);
ans[p] += af + siz[p]; // 根的贡献
for(int i = head[p];i;i = edg[i].next) {
int to = edg[i].to; if(vis[to]) continue;
af -= branf[to];
dfs2(to, p, to);
dfs3(to, p, to);
dfs4(to, p);
af += branf[to];
branf[to] = 0;
}
col[a[p]] = 0;
for(int i = 1; i <= tot; ++ i) colf[tcol[i]] = 0;
tot = 0;
for(int i = head[p];i;i = edg[i].next) {
int to = edg[i].to; if(vis[to]) continue;
Mx = 2e9, All = siz[to];
getrt(to, p);
dfs(rt);
}
}
signed main() {
// freopen("S.in", "r", stdin);
// freopen("S.out", "w", stdout);
int i, j;
r1(n);
for(i = 1; i <= n; ++ i) r1(a[i]);
for(i = 1; i < n; ++ i) {
int u, v;
r1(u, v);
add(u, v), add(v, u);
}
Mx = 2e9, All = n;
getrt(1, 0);
dfs(rt);
for(i = 1; i <= n; ++ i) printf("%lld\n", ans[i]);
return 0;
}

本文详细解析了luogu P2664树上游戏的题解,通过离线算法和点分治方法解决树上颜色序列统计问题。介绍了如何在O(n)或O(nlogn)复杂度下,统计以节点为LCA的点对贡献,利用开桶技术避免暴力计算,并在DFS过程中处理根到每个节点的贡献。
2329

被折叠的 条评论
为什么被折叠?



