【POJ 1848】Tree【树形DP】

来源:互联网 发布:送女生生日礼物 知乎 编辑:程序博客网 时间:2024/05/16 15:20
题意:给定一棵树,要添加一些边,不能是重边,不能是自环,让它的每一个结点都恰好属于一个环,求最少添加的边数。
思路:树形DP, 定义dp[i][0]表示i为根的子树所满足条件的最优解,dp[i][1]表示以i为根的子树除了i节点外的点满足条件的最优解,dp[i][2]表示以i为根的子树,除了i节点以及它的一个直接孩子节点除外的点满足条件的最优解。
转移方程:dp[i][1] = ∑dp[k][0] (k为i的孩子节点),
                    dp[i][2] = (∑dp[k][0])-dp[j][0]+min(dp[j][1], dp[j][2]),
                    dp[i][0] =  min(min((∑dp[k][0])-dp[j][0]+dp[j][2]+1), min((∑dp[k][0])-dp[j][0]-dp[h][0]+min(dp[j][1], dp[j][2])+min(dp[h][1], dp[h][2]))
第一个转移方程显然成立,第二个转移方程相当于在i的孩子中找到j使得i, j不在环中,也就是对于j为根的子树中,j不在环中所需要的边数最小值,也就是min(dp[j][1], dp[j][2])。
第三个转移方程,分为两种情况,在i的孩子中找到一个j并且使得j的一个孩子与j都不在换中,然后这三个点连成环,即min((∑dp[k][0])-dp[j][0]+dp[j][2]+1), 第二种情况就是寻找i的两个孩子,使得这三个点在同一个环中,也就是后面的式子了。
#include <cstdio>#include <cstring>#include <vector>#include <algorithm>using namespace std;#define N 110#define inf 10000vector<int>V[N];int dp[N][3];void dfs(int u, int f) {    int i, j, v, sum = 0;    vector<int>sun;    for (i = 0;i < V[u].size();i++) {        v = V[u][i];        if (v == f) continue;        dfs(v, u);        sun.push_back(v);        sum += dp[v][0];    }    if (sun.size() == 0) {        dp[u][1] = 0, dp[u][0] = inf, dp[u][2] = inf;        return;    }    dp[u][1] = sum;    int t1, t2;    t2 = t1 = inf;    for (i = 0;i < sun.size();i++) {        v = sun[i];        t2 = min(t2, sum-dp[v][0]+min(dp[v][1], dp[v][2]));        t1 = min(t1, sum-dp[v][0]+dp[v][2]+1);        for (j = i+1;j < sun.size();j++) {            int vv = sun[j];            t1 = min(t1, sum-dp[v][0]-dp[vv][0]+1+min(dp[v][1],dp[v][2])+min(dp[vv][1], dp[vv][2]));        }    }    dp[u][0] = t1, dp[u][2] = t2;}int main() {    int n, i, j, u, v;    while (~scanf("%d", &n)) {        for (i = 1;i <= n;i++) V[i].clear();        for (i = 1;i < n;i++) {            scanf("%d%d", &u, &v);            V[u].push_back(v), V[v].push_back(u);        }        dfs(1, -1);        if (dp[1][0] >= inf) puts("-1");        else printf("%d\n", dp[1][0]);    }}


0 0