目录
一.题目
题目描述
大约30年前,年轻的Krešo首次参加了全国信息学竞赛。与今天相似的,比赛的开幕都是由一系列演讲者组成,他们试图通过演讲激励参加者们并展现竞赛的重要性。观众们热情地每隔几秒钟鼓掌一次,但Krešo被其中一位发言者的一句话激怒了,因为这位发言者声称他更赞赏逻辑运算而非逻辑运算,因为无论获胜者是谁,Mirko和Slavko都会是这次竞赛的获胜者,而不是Mirko或Slavko。Krešo这时站起来,开始向大家解释一种名为“异或”的东西。在他的演讲结束后,他给尊敬的演讲者布置了这样一个任务来验证他的解释。
存在由n个节点组成的树,其中每个节点分配一个值,这个树上的路径值定义为这条路所有节点的值的异或。你的任务是确定树上所有路径的值的总和(这里的路径包括只有一个节点的路径)。
30年后,Krešo终于说服COCI的出题人将这个任务纳入其中一环,让我们恢复Krešo对编程竞赛未来的信心。
输入格式
第一行包含正整数n(1≤n≤100000),表示这棵树上的节点数。
第二行包含n个数字vi(0≤vi≤3000000)第i个数字表示第i个节点的价值。
接下来n-1行,每行输入两个数字aj和bj(1≤aj,bj≤n),表示在节点aj和bj之间有一条边。
输出格式
输出一个数,表示这棵树的价值。
样例输入
5
2 3 4 2 1
1 2
1 3
3 4
3 5
样例输出
64
二.题解
这道题我考试的时候没有想出来,简直蒟蒻。
他们都说是点分制+树的重心,但我就是不会打,在网上终于找到了树形DP的做法,蒟蒻在这里复述一下。
首先,要定义一个DP数组,怎么定义呢?这就要看题目了。
题目要求我们求所有路径的异或和,所以dp的第一维我们定义以i为根的子树,然后第二三位就是第j位为1或0的方案数,再者2^22刚好大于v[i]的最大值,j<=22。
那么dp[i][j][k]就表示:以i为根的子树内有多少条路径的异或和的第j位为k(0/1)。(大家感性理解一下)
怎么转移呢,当然是从他的子树转移过来了。
下面是我的树形DP。
void Tree_dp (int x, int fa){
for (int i = 0; i <= 22; i ++)//预处理v[x]本身的二进制的每一位是否为1/0.
dp[x][i][((v[x] >> i) & 1)] ++;
for (int i = 0; i < G[x].size (); i ++){
int u = G[x][i];
if (fa != u){
Tree_dp (u, x);
for (int j = 0; j <= 22; j ++){
ans += (dp[x][j][0] * dp[u][j][1] + dp[u][j][0] * dp[x][j][1]) * (1 << j);
//这里十分令人费解,其实他就是想要把每条路径的每一位分开算,
//所以在二进制意义下要把这一位扩大1<<j倍才能把这一位还原到原来的位置
if (((v[x] >> j) & 1))//如果v[x]的第j位为1异或起来之后就能把1变成0,把0变成1
dp[x][j][0] += dp[u][j][1], dp[x][j][1] += dp[u][j][0];
else
dp[x][j][0] += dp[u][j][0], dp[x][j][1] += dp[u][j][1];
}
}
}
}
大家可能有不理解的地方,我写了注释。
大家意会意会。
三.Code
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
#define LL long long
#define M 100005
vector <int> G[M];
int n;
LL v[M], dp[M][25][2], ans;
void Tree_dp (int x, int fa){
for (int i = 0; i <= 22; i ++)
dp[x][i][((v[x] >> i) & 1)] ++;
for (int i = 0; i < G[x].size (); i ++){
int u = G[x][i];
if (fa != u){
Tree_dp (u, x);
for (int j = 0; j <= 22; j ++){
ans += (dp[x][j][0] * dp[u][j][1] + dp[u][j][0] * dp[x][j][1]) * (1 << j);
if (((v[x] >> j) & 1))
dp[x][j][0] += dp[u][j][1], dp[x][j][1] += dp[u][j][0];
else
dp[x][j][0] += dp[u][j][0], dp[x][j][1] += dp[u][j][1];
}
}
}
}
int main (){
scanf ("%d", &n);
for (int i = 1; i <= n; i ++){
scanf ("%lld", &v[i]);
ans += v[i];
}
int u, v;
for (int i = 1; i < n; i ++){
scanf ("%d %d", &u, &v);
G[u].push_back (v);
G[v].push_back (u);
}
Tree_dp (1, 0);
printf ("%lld\n", ans);
return 0;
}