【题解】poj1947 树形DP

题目链接
dp[root][j]:以root为根节点的子树,得到 j 个节点的子树需要最少减掉的边数,注意子树中必须保留root节点。否则无法dp
那么很明显的边界条件dp[root][1] = num(儿子的个数),因为要只剩一个节点的子树,那么所有的孩子都减掉,这样就为儿子的个数。
那么状态转移方程呢
dp[root][i] = min(dp[root][i-k]+dp[child][k] - 1,dp[root][i]);
其实就是要得到一个i个节点的子树,枚举所有的孩子为k个节点的,当前root保留 i-k 个节点,然后把root和child之间之前被剪断的连接起来,所以这里要减1
注意非根节点的答案要加一

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
const int N=200;
using namespace std;
int num[N];//num[i]表示i节点的子节点个数
int sum[N];//sum[i]以i为根的树的所有节点个数
int dp[200][200];//dp[i][j]以i为根的树还剩j个节点所删去边的最小值
vector<int>v[200];//v[x][y]x与y相连
void dfs(int root)
{
    sum[root]=1;//漏了,根节点本身算一个 
    if(v[root].size()==0)//叶子节点 
    {
        sum[root]=1;
        dp[root][1]=0;
        return;
    }
    for(int i=0;i<v[root].size();i++)//枚举root的子节点 
    {
        int child=v[root][i];//子节点
        dfs(child);
        sum[root]+=sum[child];
        for(int j=sum[root];j>0;j--)
        for(int k=1;k<j;k++)
        dp[root][j]=min(dp[root][j],dp[root][j-k]+dp[child][k]-1); 
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    memset(num,0,sizeof(num));
    memset(sum,0,sizeof(sum));
    memset(dp,0x3f,sizeof(dp));
    int n,p,i,x,y;
    scanf("%d%d",&n,&p);//又漏了这句 
    for(i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        v[x].push_back(y);
        num[x]++;
    }
    for(i=1;i<=n;i++)
    dp[i][1]=num[i];//将子节点全部删完
    dfs(1);
    int ans=dp[1][p];
    for(i=2;i<=n;i++)//扫描找最小值 
    ans=min(ans,dp[i][p]+1);//漏了+1,把根节点和父节点断开 
    printf("%d\n",ans);
    return 0;
}   

猜你喜欢

转载自blog.csdn.net/qq_41958841/article/details/81636796