2020 CCPC Changchun F. Strange Memory【dsu on tree】

本文介绍了一种解决特定树形结构问题的方法,通过树形DP与启发式合并技术来高效计算节点间的异或表达式。利用轻重儿子的概念进行启发式合并,通过维护计数数组来加速查找过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

给一颗 n n n个节点的树,节点 u u u有权值 a [ u ] ( 1 ≤ a [ u ] ≤ 1 e 6 ) a[u](1 \le a[u] \le 1e6) a[u](1a[u]1e6),计算下列表达式:
∑ i = 1 n ∑ j = i + 1 n [ a [ i ]   x o r   a [ j ] = = a [ l c a ( i , j ) ] ] ( i   x o r   j ) \sum_{i=1}^{n}\sum_{j=i+1}^{n}[a[i]\ xor\ a[j]==a[lca(i,j)]](i\ xor\ j) i=1nj=i+1n[a[i] xor a[j]==a[lca(i,j)]](i xor j)

思路

由于 a [ u ] ≠ 0 a[u] \ne 0 a[u]=0,于是不存在 i , j , l c a ( i , j ) i,j,lca(i,j) i,j,lca(i,j)在一条链上的情况,于是可以考虑枚举以 l c a ( i , j ) lca(i,j) lca(i,j)为根的子树然后计算贡献。
由于 a [ i ]   x o r   a [ j ] = = a [ l c a ( i , j ) ] a[i]\ xor\ a[j] == a[lca(i,j)] a[i] xor a[j]==a[lca(i,j)]等价于 a [ i ] = = a [ j ]   x o r   a [ l c a ( i , j ) ] a[i] == a[j]\ xor\ a[lca(i,j)] a[i]==a[j] xor a[lca(i,j)],现在考虑某颗以 u u u为根的子树对答案的贡献如何计算。一个容易想到的思路是,按照顺序遍历以 u u u的孩子 v v v为根的子树,对于 v v v子树内的某一个节点 j j j,令 x = a [ j ]   x o r   a [ u ] x=a[j]\ xor\ a[u] x=a[j] xor a[u],从已遍历的孩子表示的子树中,找到所有权值为 x x x的节点 i i i,将答案加上 i   x o r   j i\ xor\ j i xor j。这样算的复杂度肯定是爆炸的。
考虑按位去算,对于满足 a [ i ] = = a [ j ]   x o r   a [ l c a ( i , j ) ] a[i] == a[j]\ xor\ a[lca(i,j)] a[i]==a[j] xor a[lca(i,j)]的下标 i i i的某一位 b i t bit bit,只有 i i i j j j这一位不同才对答案有贡献,于是我们可以记录 c n t [ x ] [ b i t ] [ k ] cnt[x][bit][k] cnt[x][bit][k]表示,当前值为 x x x的下标中,第 b i t bit bit位为 k k k的下标个数。
接下来就是树上启发式合并的经典操作了,首先计算所有轻儿子对答案与 c n t cnt cnt数组的贡献,然后把轻儿子对 c n t cnt cnt数组的贡献去掉,计算重儿子对答案与 c n t cnt cnt数组的贡献即可。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <vector>
#include <map>
#include <queue>
#include <cmath>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 100005;

int n;
int a[N];
struct edge
{
    int to, next;
}e[N << 1];
int head[N], tot;
int cnt[1 << 20][20][2];
int siz[N], son[N];
bool vis[N];
LL ans;

void add(int u, int v)
{
    e[++ tot] = {v, head[u]};
    head[u] = tot;
}

void dfs(int u, int ff)
{
    siz[u] = 1;
    for (int i = head[u]; i; i = e[i].next)
    {
        int v = e[i].to;
        if (v == ff) continue;
        dfs(v, u);
        siz[u] += siz[v];
        if (!son[u] || siz[v] > siz[son[u]]) son[u] = v;
    }
}

void modify(int u, int ff, int val)
{
    for (int i = 0; i < 20; i ++ )
        cnt[a[u]][i][u >> i & 1] += val;
    for (int i = head[u]; i; i = e[i].next)
    {
        int v = e[i].to;
        if (v == ff) continue;
        modify(v, u, val);
    }
}

void updateAns(int u, int ff, int lca)
{
    int x = a[u] ^ a[lca];
    for (int i = 0; i < 20; i ++ )
        ans += (1LL << i) * cnt[x][i][u >> i & 1 ^ 1];
    for (int i = head[u]; i; i = e[i].next)
    {
        int v = e[i].to;
        if (v == ff) continue;
        updateAns(v, u, lca);
    }
}

void dfs2(int u, int ff, int keep)
{
    for (int i = head[u]; i; i = e[i].next)
    {
        int v = e[i].to;
        if (v == ff || son[u] == v) continue;
        dfs2(v, u, 0);
    }
    if (son[u]) dfs2(son[u], u, 1), vis[son[u]] = true;
    
    for (int i = head[u]; i; i = e[i].next)
    {
        int v = e[i].to;
        if (v == ff || vis[v]) continue;
        updateAns(v, u, u);
        modify(v, u, 1);
    }
    for (int i = 0; i < 20; i ++ )
        cnt[a[u]][i][u >> i & 1] ++;
    vis[son[u]] = false;

    if (!keep)
        modify(u, ff, -1);
}

int main()
{
    scanf("%d", &n);
    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);
        add(u, v);
        add(v, u);
    }

    dfs(1, 0);
    dfs2(1, 0, 0);

    printf("%lld\n", ans);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值