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

謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章