【一本通提高篇】【题解】皇宫看守(树形DP总结)

【一本通提高篇】【题解】皇宫看守(树形DP总结)_第1张图片【一本通提高篇】【题解】皇宫看守(树形DP总结)_第2张图片思路:

做过最大独立集,最小点覆盖,最小支配集,树上背包,普通树形DP,这就是树形DP的主要方式。
最小支配集是选择最少的点去覆盖所有的点,每个选择的点可以覆盖相连的点和它本身。
首先,最小支配集的状态设置就有难度,不只是用子节点跟新父亲节点,父亲的状态也会影响答案。
那么我们这样定义状态:
有三种状态:
1.选父亲节点,不选本节点。
2.选子节点,不选本节点。
3.选本节点。
那么:
dp[x][0]表示选父亲节点的最小花费;
dp[x][1]表示选子节点的最小花费(不一定要全选,只要合法就可以了);
dp[x][2]表示选本节点的最小花费;

那么来考虑状态转移:
【一本通提高篇】【题解】皇宫看守(树形DP总结)_第3张图片对于dp[x][0]:
for(son) dp[x][0]+=min(dp[y][1],dp[y][2]);
对于dp[x][1]:
for(son) dp[x][1]+=min(dp[y][1],dp[y][2]);
注意这个方程是不一定合法的,若x节点所有的儿子的dp[x][1]都比dp[x][2]小,就没有节点能看到x节点了,所以我们必须在x的子节点中强制选一个。
所以这里就有一个小技巧:
设d;
d=min(d,dp[y][2]-min(dp[y][1],dp[y][2]));
自己推一边就会发现:若存在一个点的dp[x][2]比dp[x][1]小,那么d就是0;
如果不存在的话,那么d就是x的子节点中dp[y][2]最小的那个值;
所以在最后把dp[x][1]再加上d就可以了。
对于dp[x][2]
若选了这个点,就一定合法,只需要选一个最小的就可以了;
for(son) dp[x][2]+=min(dp[y][1],dp[y][2],dp[y][0]);

在最后的输出的时候要注意:根节点是没有父亲的,所以只用输出min(dp[root][1],dp[root][2]);
代码如下:

#include 

using namespace std;

const int maxn = 1600;
int n, k, head[maxn], cnt = 0;

int dp[maxn][3], val[maxn];

struct edge {
    int to, pre;
} e[maxn * 2];

inline void add(int x, int y) {
    e[++cnt].pre = head[x];
    e[cnt].to = y;
    head[x] = cnt;
}

void dfs(int x, int fa) {
    int d = 0x7fffffff / 2;
    for (int i = head[x]; i; i = e[i].pre) {
        int v = e[i].to;
        if (v == fa)
            continue;
        dfs(v, x);
        dp[x][0] += min(dp[v][2], dp[v][1]);
        dp[x][1] += min(dp[v][2], dp[v][1]);
        d = min(d, dp[v][2] - min(dp[v][1], dp[v][2]));
        dp[x][2] += min(dp[v][1], min(dp[v][2], dp[v][0]));
    }
    dp[x][2] += val[x];
    dp[x][1] += d;
}
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        int x, z, y;
        scanf("%d%d%d", &x, &k, &z);
        val[x] = k;
        for (int j = 1; j <= z; ++j) {
            scanf("%d", &y);
            add(x, y);
            add(y, x);
        }
    }
    dfs(1, 0);
    printf("%d", min(dp[1][1], dp[1][2]));
    return 0;
}

你可能感兴趣的:(树上,DP,算法)