poj1947——rebuilding roads

来源:互联网 发布:mac 屏幕录像 编辑:程序博客网 时间:2024/05/17 23:26

题目大意:给出一棵树,问最少减去几条边,能得到一个有p个结点的子树

输入:原树结点个数n p(1<=n<=150)

           结点i(父亲) i的儿子j

输出:最少减去的边数

分析:树形动态规划

           状态dp[i][j]:以i为根节点,要得到j个结点的子树去掉的最少边数(注意子树中包含i结点)

           状态转移方程:对于结点i,要么删掉它的某个儿子c,即删掉i_c一条边,要么在以c为根的子树中选出一些边删掉,构造出有k个结点的子树,所以方程为dp[i][j]=min{dp[i][j]+1,dp[i][j-k]+dp[c][k]},c为i的儿子

           结果:min{dp[root][p],dp[i][p]+1},i为除了根节点的其他结点

           初始化:dp[i][1]=i结点的儿子个数(要只剩一个结点即i本身,就要剪掉i的所有儿子)

代码:转载自http://www.cnblogs.com/20143605--pcx/p/5348610.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# include<iostream>
# include<cstdio>
# include<cstring>
# include<vector>
# include<algorithm>
using namespace std;
 
const int N=155;
const int INF=1000000000;
 
int n,m;//结点个数,要得到的子树结点个数
bool flag[N];//标志是否有父节点
int dp[N][N];
vector<int>e[N];
 
void init()
{
    int a,b;
    for(int i=1;i<=n;++i){
        e[i].clear();
        for(int j=0;j<=m;++j)
            dp[i][j]=INF;
    }
    memset(flag,false,sizeof(flag));
    for(int i=1;i<n;++i){
        scanf("%d%d",&a,&b);
        e[a].push_back(b);
        flag[b]=true;
    }
}
 
void dfs(int u)
{
    dp[u][1]=0;
    for(int i=0;i<e[u].size();++i){
        int v=e[u][i];
        dfs(v);
        for(int j=m;j>=1;--j){
            dp[u][j]+=1;//直接减去子树v的情况,去掉的边数+1
            for(int k=1;k<j;++k){    ///k从1循环到j-1,一定不能从0循环到j
                dp[u][j]=min(dp[u][j],dp[v][k]+dp[u][j-k]);//以v结点为根得到k个结点的子树
            }
        }
    }
}
 
void solve()
{
    int ans=INF;
    for(int i=1;i<=n;++i){
        if(flag[i]) continue;//找到整棵树的根节点
        dfs(i);//遍历根节点
        ans=dp[i][m];
        break;
    }
    for(int i=1;i<=n;++i)
        ans=min(ans,dp[i][m]+1);//除了根节点以外的其他任意结点想要成为独立的根,都要剪掉与总根之间的边,所以要+1
    printf("%d\n",ans);
}
 
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        solve();
    }
    return 0;
}