COCI 2018/2019 Deblo —— 树形DP

目录

一.题目

题目描述

输入格式

输出格式

样例输入

样例输出

二.题解 

三.Code

谢谢!


一.题目

题目描述

大约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;
}

谢谢!

发布了61 篇原创文章 · 获赞 32 · 访问量 8341

猜你喜欢

转载自blog.csdn.net/weixin_43908980/article/details/100006849