Deblo

题目

题目描述

大约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才可以过

总结

最后总结一下

这次考试不应考这么差,我觉得还有两个问题

一.是自己态度没摆正,以为打了随便测了一下数据就可以过

二.思维还不够灵活,想一个爆搜都想了好久,又打错了

三.在考场上,时间安排很重要,因为一旦没安排好,或者只安排思考时间,不行

遇到没思路的题先跳,如果有一些思路,更要深层思考,要借助草稿本画图,比如今天第二题与第四题

 

发布了68 篇原创文章 · 获赞 7 · 访问量 3843

猜你喜欢

转载自blog.csdn.net/weixin_43823476/article/details/100006830