题目
题目描述
大约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之间有一条边。
输出格式
输出一个数,表示这棵树的价值。
样例
样例输入1
3
1 2 3
1 2
2 3
样例输出1
10
样例输入2
5
2 3 4 2 1
1 2
1 3
3 4
3 5
样例输出2
64
样例输入3
6
5 4 1 3 3 3
3 1
3 5
4 3
4 2
2 6
样例输出3
85
数据范围与提示
在第一组样例中:
从1到1:1
从1到2:1⊕2=3
从1到3:1⊕2⊕3=0
从2到2:2
从2到3:2⊕3=1
从3到3:3
它们的总和为1+3+0+2+1+3=10
题解
看到异或,感觉自己都蒙了
然后就不知道在干嘛的乱搞
结果自己爆搜都没打出来
现在如果再想,还是有一定思路的
如果我们是从根到叶子依次算且不重复,可以发现一个子节点不会搜索到比它叶子节点更高的点
也就是以这个子节点为起点的所有不重复路径中,最高点一定是在其父亲
所以用这个思路写dp,又因为有异或这种二进制运算,且他不是求最大最少,而是求和,所以线性基是不可以用的
我们可以将其二进制分组
dp[i][j][0/1]表示路径最高点位i且路径值转化为二进制后第j位0/1的条数
那么看到2^22就大于最大点权了
我们进行转移
for( int j = 0 ; j < 23 ; j ++ ){
ll &x0 = dp[x][j][0];
ll &x1 = dp[x][j][1];
ll &z0 = dp[v][j][0];
ll &z1 = dp[v][j][1];
ans += ( x0 * z1 + x1 * z0 ) * ( 1ll << j );//根据异或性质,只有第j位不同的两段路径异或会有值
if( ((V[x]>>j)&1) ){
x0 += z1 , x1 += z0;
}
else
x0 += z0 , x1 += z1;
}
然后就差不多了
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
#include <set>
#include <queue>
#include <stack>
#define ll long long
using namespace std;
const int MAXN = 100003;
int n ;
ll V[MAXN];
vector<int>G[MAXN];
ll ans = 0;
ll dp[MAXN][24][2];
void dfs( int x , int fa ){
for( int i = 0 ; i < 23 ; i ++ ){
dp[x][i][((V[x]>>i)&1)] = 1;
}
for( int i = 0 ; i < G[x].size() ; i ++ ){
int v = G[x][i];
if( v == fa ) continue;
dfs( v , x );
for( int j = 0 ; j < 23 ; j ++ ){
ll &x0 = dp[x][j][0];
ll &x1 = dp[x][j][1];
ll &z0 = dp[v][j][0];
ll &z1 = dp[v][j][1];
ans += ( x0 * z1 + x1 * z0 ) * ( 1ll << j );
if( ((V[x]>>j)&1) ){
x0 += z1 , x1 += z0;
}
else
x0 += z0 , x1 += z1;
}
}
}
int main()
{
scanf( "%d" ,&n );
for( int i =1 ; i <= n ; i ++ ){
scanf( "%d" , &V[i] );
ans += V[i];
}
for( int i = 1 ; i < n ; i ++ ){
int x , y;
scanf( "%d%d" , &x , &y );
G[x].push_back( y );
G[y].push_back( x );
}
dfs( 1, 0 );
printf( "%lld\n" , ans );
return 0;
}
这道题不知道为什么很卡时间,也就是只有2^22才可以过
总结
最后总结一下
这次考试不应考这么差,我觉得还有两个问题
一.是自己态度没摆正,以为打了随便测了一下数据就可以过
二.思维还不够灵活,想一个爆搜都想了好久,又打错了
三.在考场上,时间安排很重要,因为一旦没安排好,或者只安排思考时间,不行
遇到没思路的题先跳,如果有一些思路,更要深层思考,要借助草稿本画图,比如今天第二题与第四题