【2019西安邀请赛J】And And And(树链贡献)

https://nanti.jisuanke.com/t/39277

题意

给出一棵树,以及树边权,求出这个公式
在这里插入图片描述

题意

这里规定了偏序关系,所以不用重复计算两次。
将这题转化为求两个点所做的贡献。
首先要明白异或的性质。
a ^ b = 0 等价于 a = b
那么a到b这条链上异或等于0等价于,a到根的异或值 = b到根的异或值。

在这里插入图片描述
观察这颗树,(1,5)符合答案,哪些链会包括(1,5)呢?
(1,5),(3,5),(4,5),(6,5)。
可以知道(1,5)出现的次数,等于1 "右边的点数" x 5 "左边的点数" = 4*1

再举个例子
(2,6)这条链异或值也为0,包含(2,6)的有 (2,6),(5,6)。
等于 2"左边的点数" x 6左边的点数" = 2*1
所以答案为 2+4 = 6。

分两种情况讨论两点(x,y) = 0所做的贡献。

  1. x 和 y 的LCA是 x或者y
  2. x 和 y 的LCA不是 x 或者 y

对于 2 ,贡献就是 x的子树大小*y的子树大小。但还有一个前提便是x到根的异或值要等于y到根的异或值。
所以需要用一个map记录异或值为 k 时的总子树大小。
设sz[x]:为x的子树大小。
k: x到根的异或值
mp[k]: 当前异或值为k的子树大小总和
dfs整棵树,到dfs到x的时候,点x的贡献便是:sz[x] * mp[k]。
因为是有序的,所以x的贡献只会算一遍。

对于1,子树的大小并不是等于"右边的点数"或者是"左边的点数"。
需要加一个临时变量 sum。
记录此时异或值为k的情况下其"右边的点数","左边的点数"还是等于子树大小。
具体看代码吧。

代码

#include <unordered_map>
#include <bits/stdc++.h>
using namespace std;
#define FOR0(a,b) for(int i = a; i < b; ++i)
#define FORE(a,b) for(int i = a; i <= b; ++i)
typedef long long ll;
typedef pair<ll,ll> pii;
const int maxn = 1e5+5;
const int mod = 1e9+7;
ll ans;
vector<pii> G[maxn];
unordered_map<ll,ll> mp;
ll sz[maxn];

void dfs(int x) {
    sz[x] = 1;
    for(pii u : G[x]) {
        dfs(u.first);
        sz[x] += sz[u.first];
    }
}

// 不同链
void dfs1(int x, ll xr) {
    
    ans = (ans+1ll*mp[xr]*sz[x])%mod;
    for(pii u : G[x]) {
        dfs1(u.first, xr^u.second);
    }
    mp[xr] = (mp[xr]+sz[x])%mod;
}
ll sum = 0;
// 同链
void dfs2(int x, ll xr) {
    ans = (ans+1ll*mp[xr]*sz[x])%mod;
    for(pii u : G[x]) {
        sum = (sum+sz[x]-sz[u.first])%mod;
        mp[xr] = (mp[xr]+sum)%mod;
        dfs2(u.first, xr^u.second);
        mp[xr] = (mp[xr]-sum)%mod;
        sum = (sum-sz[x]+sz[u.first])%mod;
    }
}
int main() {
    int n;
    scanf("%d", &n);
    for(int i = 2; i <= n; ++i) {
        int fa;
        ll w;
        scanf("%d%lld", &fa, &w);
        G[fa].push_back(pii(i,w));
    }
    dfs(1);
    dfs1(1,0);
    // printf("%lld\n", ans);
    mp.clear();
    dfs2(1,0);
    printf("%lld\n", ans);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值