树形DP

树形DP

例题 - Barn Painting
USACO Gold - 普及+/提高
例题 - 请努力完成本题,再阅读以下内容。解答

树形 DP,即在树上进行的 DP。由于树固有的递归性质,树形DP一般都是递归进行的,基本方式是通过DFS遍历树,然后从叶子开始计算DP状态。 状态一般定义在节点或者子树上。

树形DP的难度往往来自以下方面:

在练习过程中,把树在纸上画出来会便于思考。对于稍大的图,CSAcademy Graph Editor 可以输入邻接表自动画图,比较方便。

解答 - Barn Painting

定义dp[i][j]\text{dp}[i][j]ii子树中颜色jj的方案数。则状态转移方程是(uuii的孩子):

dp[i][j]=ukjdp[u][k]\text{dp}[i][j] = \prod_u \sum_{k \neq j} \text{dp}[u][k]
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
int n,K;
int dp[100005][3];      // subtree count for 3 colors
int cl[100005];         // -1 or 0-2
vector<int> adj[100005];
const int MOD = 1e9 + 7;
 
void dfs(int s, int e) {
    dp[s][0] = dp[s][1] = dp[s][2] = 1;
    for (int u: adj[s]) {
        if (u == e) continue;
        dfs(u, s);
    }
    for (int i = 0; i < 3; i++)
        for (int u: adj[s]) {
            if (u == e) continue;
            int sum = 0;
            for (int j = 0; j < 3; j++) {
                if (i == j) continue;
                sum = ((ll)sum + dp[u][j]) % MOD;
            }
            dp[s][i] = ((ll)dp[s][i] * sum) % MOD;  // multiply all children color combinations
        }
    for (int i = 0; i < 3; i++)
        if (cl[s] >= 0 && i != cl[s])       // apply current node color
            dp[s][i] = 0;
}
 
int main() {
    scanf("%d %d", &n, &K);
    for (int i = 0; i < n-1; i++) {
        int u,v;
        scanf("%d %d", &u, &v);
        u--,v--;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }
    fill(cl, cl+n, -1);
    for (int i = 0; i < K; i++) {
        int b,c;
        scanf("%d %d", &b, &c);
        cl[b-1] = c-1;
    }
    dfs(0, -1);
    printf("%lld", ((ll)dp[0][0] + dp[0][1] + dp[0][2]) % MOD);
 
    return 0;
}
习题
POIParade2016普及+/提高解答
CF161DCFDistance in Tree提高+/省选-解答
BalticOIVillage (Minimum)2020提高+/省选-解答
P5021NOIP-S赛道修建2018提高+/省选-
P3177HAOI树上染色2015提高+/省选-
P6147USACO GoldDelegation提高+/省选-
CFBerland Federalization提高+/省选-解答
CFParsa's Humongous Tree提高+/省选-解答