题意:
给定一颗n个结点的树(n-5e5),每个结点有个权值ai(ai-1e9)
现在定义一个值 path[i][j] 表示i结点到j结点最短路径上 经过的所有结点权值的异或和
现在要求计算所有的 path[i][j] 异或和,i 范围1~n-1, j 范围 i+1 ~ n
思路:
首先树上最短路径是唯一的,即两个点只有一条路径(每个点只能走一次的话);
然后考虑到计算所有path值的异或和的时候,我们会发现,最后的答案就是每跳路径中经过的点的权值的异或和
然后我们可以算每个结点在所有路径中出现的次数,也就是算他的贡献次数,
至于这个点贡献了多少次,可以分为两部分:一部分是在其他两点路径上,另一部分是这个点作为path的开头
第一部分的值就是这个点连接的每个点及后面的点看作子树,和另外的所有的点算一下,相当于贡献次数,每个结点都算
第二部分都是n-1次;
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
const ll maxn = 1e6 + 7;
ll n;
vector<ll> vec[maxn];
ll a[maxn];
ll ans = 0;
ll dfs(ll id, ll f) {
ll sum = 0;
ll t = 0;
ll res = 1; // 子树大小
for(auto i : vec[id]) {
if(i == f) continue;
t = dfs(i, id);
res += t;
sum += (t*(n-t-1)); //sum %= 2LL;
}
sum += (n-res)*(res-1); sum /= 2LL;//sum %= 2LL;
sum += (n-1); sum %= 2LL;
if(sum&1) ans ^= a[id];
return res;
}
int main() {
scanf("%lld", &n);
for(int i = 1; i < n; ++i) {
ll x, y; scanf("%lld%lld", &x, &y);
vec[x].push_back(y);
vec[y].push_back(x);
}
for(int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
}
int x = dfs(1, -1);
printf("%lld", ans);
return 0;
}