POJ-1947 Rebuilding Roads

题目给出一棵树,求切出一棵节点数为p的新树最少要切多少条边。

比较明显的树形DP,dp[i][j]为第i个节点为根的子树具有j个节点最少要切的边数。

初始化的时候不应该考虑子树需要切断与父亲的边才能成树这一情况,因为dp在转移的时候是把子树当成与当前节点连接着来考虑的,这样写起来更加方便。最后更新ans的时候再考虑是不是子树就好。

考虑一个节点,有dp[now][0]=0。对于它的子树,如果不要,则有dp[i][0]=dp[i][0]+1(因为不要,则数目不变,但耗费+1);如果要这颗子树,这里需要处理要多少的问题,即再开一层循环,dp[now][j]=min(*,dp[child][k]+dp[now][j-k])

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include <string>
#include<queue>
#include<vector>
#include<set>
#include<map>
#define LL long long
using namespace std;
const int N = 160;
const LL inf = 9999999;
LL n, p;
LL dp[N][N];
LL cnt[N];
vector<int> g[N];
LL ans;
int rt;
void dfs(int now)
{
    for (int i = 0; i < g[now].size(); i++)
    {
        int nx = g[now][i];
        dfs(nx);
        cnt[now] += cnt[nx];
    }
    fill(dp[now], dp[now] + N, inf);
    dp[now][1] = 0;
    for (int i = 0; i < g[now].size(); i++)
    {
        //LL temp[N];
        //fill(temp, temp + N, inf);
        int e = g[now][i];
        for (int j = p; j>=1; j--)
        {
            LL temp = dp[now][j]+1;
            for (int k = 1;k<j; k++)
                temp = min(temp, dp[now][j - k] + dp[e][k]);
            dp[now][j] = temp;
        }
        //for (int j = 1; j <= cnt[now]; j++)
            //dp[now][j] = temp[j];
    }
    if(now==rt)
        ans = min(ans, dp[now][p]);
    else ans = min(ans, dp[now][p] + 1);
}
int  main()
{
    while (cin >> n >> p)
    {
        ans = inf;
        bool vis[N];
        fill(vis, vis + N, true);
        for (int i = 0; i < N; i++) g[i].clear();
        for (int i = 0; i < n - 1; i++)
        {
            int x, y;
            cin >> x >> y;
            g[x].push_back(y);
            vis[y] = false;
        }
        for (int i = 1; i <= n; i++)if (vis[i]) rt = i;
        fill(cnt, cnt + N, 1);
        dfs(rt);
        cout << ans << endl;
    }

    return 0;
}
原文地址:https://www.cnblogs.com/LukeStepByStep/p/7326139.html