树形DP
例题 - Barn Painting USACO Gold - 普及+/提高 | |
例题 - 请努力完成本题,再阅读以下内容。 | 解答 |
树形 DP,即在树上进行的 DP。由于树固有的递归性质,树形DP一般都是递归进行的,基本方式是通过DFS遍历树,然后从叶子开始计算DP状态。 状态一般定义在节点或者子树上。
树形DP的难度往往来自以下方面:
- 状态和转移的设计: 路径和子树会有不同形状,所以经常要定义多个DP状态,以及状态转移会有较多细节。
- 实现难度:如果题目涉及到树的多种形态,树的结构的修改,往往会有一定的实现难度,注意不要漏掉可能性。
- 此外,树上DP经常结合greedy算法,怎样构造正确而且容易实现的greedy算法,也需要多做题来体会。
在练习过程中,把树在纸上画出来会便于思考。对于稍大的图,CSAcademy Graph Editor 可以输入邻接表自动画图,比较方便。
解答 - Barn Painting
定义是子树中颜色的方案数。则状态转移方程是(是的孩子):
#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;
}
习题 | ||||||
---|---|---|---|---|---|---|
POI | Parade | 2016 | 普及+/提高 | 解答 | ||
CF161D | CF | Distance in Tree | 提高+/省选- | 解答 | ||
BalticOI | Village (Minimum) | 2020 | 提高+/省选- | 解答 | ||
P5021 | NOIP-S | 赛道修建 | 2018 | 提高+/省选- | ||
P3177 | HAOI | 树上染色 | 2015 | 提高+/省选- | ||
P6147 | USACO Gold | Delegation | 提高+/省选- | |||
CF | Berland Federalization | 提高+/省选- | 解答 | |||
CF | Parsa's Humongous Tree | 提高+/省选- | 解答 |