Loading

洛谷P1272 重建道路(树形DP+分组背包)

题目描述

一场可怕的地震后,人们用N个牲口棚(1≤N≤150,编号1..N)重建了农夫John的牧场。由于人们没有时间建设多余的道路,所以现在从一个牲口棚到另一个牲口棚的道路是惟一的。因此,牧场运输系统可以被构建成一棵树。John想要知道另一次地震会造成多严重的破坏。有些道路一旦被毁坏,就会使一棵含有P(1≤P≤N)个牲口棚的子树和剩余的牲口棚分离,John想知道这些道路的最小数目。

输入格式

第1行:2个整数,N和P

第2..N行:每行2个整数I和J,表示节点I是节点J的父节点。

输出格式

单独一行,包含一旦被破坏将分离出恰含P个节点的子树的道路的最小数目。

输入输出样例

输入 #1
11 6
1 2
1 3
1 4
1 5
2 6
2 7
2 8
4 9
4 10
4 11
输出 #1
2
不是很明白为啥题解都热衷于建无向图...这明明是一棵有根树啊...直接找到根节点dfs就可以了。
dp[i][j]:i这个节点得到j个节点的子树(包括自己)且与i节点的父亲节点相连所需要删除的最少边数 转移方程在熟悉的套路上稍作改动: dp[x][j]=min(dp[x][j],dp[x][j-k]+dp[y][k]-1);因为在初始化的时候都是与儿子不相连的,去掉的边也累计进去了,但转移的时候要连起来因此要把这一条边补上。
还有就是最后答案是max(dp[i][p])而非dp[root][p]。
#include <bits/stdc++.h>
#define N 310
using namespace std;
int n,p,head[N],ver[N],Next[N],tot=0,size[N]={0};
int dp[N][N]={0};//dp[i][j]:i这个节点得到j个节点的子树(包括自己)且与i节点的父亲节点相连所需要删除的最少边数 
bool vis[N]={0};
void add(int x,int y)
{
    ver[++tot]=y,Next[tot]=head[x],head[x]=tot;    
 } 
void dfs(int x)
 {
     //初始化
     dp[x][1]=size[x];//与所有儿子断绝关系 
     int i,j,k;
     for(i=head[x];i;i=Next[i])
     {
         int y=ver[i];
         dfs(y);
        for(j=p;j>=1;j--)//背包容量 其实直接从p开始就好 没必要从前xx棵子树的节点总数开始 从大到小枚举是因为压缩掉了 前xx棵子树 这一维度 
        {
            for(k=1;k<j;k++)
            {
                dp[x][j]=min(dp[x][j],dp[x][j-k]+dp[y][k]-1);
            }
        }
    }
 }
int main()
{
    cin>>n>>p;
    int i;
    memset(dp,0x3f3f3f3f,sizeof(dp));//注意初始化 
    memset(size,0,sizeof(size));
    memset(vis,0,sizeof(vis));
    for(i=1;i<=n-1;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        size[x]++;//儿子个数 
        vis[y]=1;//是儿子的就标记上 
    }
    int root;
    for(i=1;i<=n;i++)
    {
        if(vis[i]==0)
        {
            root=i;
            break;
        }
        dp[i][1]=size[i];
    }
    dfs(root);
    int ans=dp[root][p]; 
    for(i=1;i<=n;i++)
    {
        if(dp[i][p]<ans)ans=dp[i][p]+1;
    }
    cout<<ans;//有问题 
} 

 

posted @ 2020-03-25 23:16  脂环  阅读(150)  评论(0编辑  收藏  举报