poj 1947 树形dp

25 篇文章 0 订阅

http://poj.org/problem?id=1947

Description

The cows have reconstructed Farmer John's farm, with its N barns (1 <= N <= 150, number 1..N) after the terrible earthquake last May. The cows didn't have time to rebuild any extra roads, so now there is exactly one way to get from any given barn to any other barn. Thus, the farm transportation system can be represented as a tree. 

Farmer John wants to know how much damage another earthquake could do. He wants to know the minimum number of roads whose destruction would isolate a subtree of exactly P (1 <= P <= N) barns from the rest of the barns.

Input

* Line 1: Two integers, N and P 

* Lines 2..N: N-1 lines, each with two integers I and J. Node I is node J's parent in the tree of roads. 

Output

A single line containing the integer that is the minimum number of roads that need to be destroyed for a subtree of P nodes to be isolated. 

Sample Input

11 6
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11

Sample Output

2

Hint

[A subtree with nodes (1, 2, 3, 6, 7, 8) will become isolated if roads 1-4 and 1-5 are destroyed.] 
/**
树形DP + 背包(题解说明为转载)
本题要求求最少删除几条边使得子树节点个数为p,我们只要算出每个以节点i为根的树中节点个数为p的最少删除边数,求个最小值就好。
每棵以i为根的树有sum种物品(sum为他以及与他的子孙节点的个数),必须要删除k条边才能使得这棵子树有j个节点(1<=j<=sum),
那么每个物品j的费用是k,价值是j,这样问题就转换为在树上的分组背包,总共有n组物品,每次都从以i为根的物品组中选择一个物品进行转移,
每组选择一个物品。由于根节点固定了是1,我把树看成有向的树,也就是每次求解都不管父节点,很多人的解题报告里都有管父亲节点,但我觉得那样不好理解。
现在设dp[i][j]表示以i为根的子树中节点个数为j的最少删除边数(从分组背包角度理解就是到转移到第i组价值为j的最少费用)
状态转移方程: dp[i][1] = tot(tot为他的子节点个数)
dp[i][j] = min(dp[i][j],dp[i][k]-1+dp[s][j-k])  (1<=i<=n,2<=j<=sum(节点总和),1<=k<j,s为i子节点)
(i中已有k个节点并从s中选择j-k个,算最少删除边数,s选上所以i->s的边不需删除,所以-1)
*/
#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>
using namespace std;
const int N=151;
struct node
{
    int v,next;
} edge[N];
int ip,n,m,a,b,ans;
int dp[N][N],num[N],head[N],sum[N];
void init()
{
    memset(num,0,sizeof(num));
    memset(head,-1,sizeof(head));
    memset(dp,0x3f3f3f3f,sizeof(dp));
    memset(sum,0,sizeof(sum));
    ip=0;
}
void addedge(int i,int j)
{
    edge[ip].v=j;
    edge[ip].next=head[i];
    head[i]=ip++;
    num[i]++;
}
void dfs(int k)
{
    sum[k]=1;
    if(head[k]==-1)
    {
        dp[k][1]=0;
        return;
    }
    for(int i=head[k]; i!=-1; i=edge[i].next)
    {
        int v=edge[i].v;
        dfs(v);
        sum[k]+=sum[v];
        for(int j=sum[k]; j>0; j--)
            for(int p=1; p<j; p++)
                dp[k][j]=min(dp[k][j],dp[k][j-p]+dp[v][p]-1);
    }
}
int main()
{
    init();
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=1; i<n; i++)
        {
            scanf("%d%d",&a,&b);
            addedge(a,b);
        }
        for(int i=1; i<=n; i++)
            dp[i][1]=num[i];
        dfs(1);
        ans=dp[1][m];
        for(int i=2; i<=n; i++)
            ans=min(ans,dp[i][m]+1);
        printf("%d\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值