luogu P2664 树上游戏 题解

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

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 ij 的颜色数量。

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) fvTf(v)vT,colvSf(v)+uS(AllSiz)

后面的就是外面的点对当前子树的总贡献。

具体来说:对于一个点 v v v 可以变成 x → r t → v x \to rt \to v xrtv。显然直接统计会有重复的贡献。从 x → r t x \to rt xrt 显然就是不在当前子树的 f f f 的贡献之和,也就是前面两项。然后这会产生重复的贡献,那么我们考虑换根,也就是最近的节点是当前遍历到的颜色和之前重复的节点,所以需要减去之前和当前颜色相同的节点的贡献,也就是第 3 3 3 项,最后一项就是从 r t → v rt \to v rtv 的所有点的贡献次数。

我们实现的时候开桶记录颜色为 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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值