http://poj.org/problem?id=1947
题意:给你一棵有N个结点的树, 要求从树中剪掉一些边, 使得最后得到的子树恰好有P个结点。
思路:树形dp。用dp[i][j] 表示在以i为根的子树中保留下j个结点最少需要剪掉的边数。这样在求i结点的不同j值时,就是对其孩子的一次背包。dp[i][j] = MIN(dp[i][j] , dp[son[i]][k]+dp[i][j-k]);
代码:
/*
树形dp
*/
#include<stdio.h>
#include<string.h>
#define MIN(a,b) (a)>(b)?(b):(a)
int N ,P ;
bool map[160][160] ;
int dp[160][160] ;
bool vis[160] ;
int root ,_min ;
void dfs(int u){
dp[u][1] = 0 ;
for(int j=2;j<=P;j++)
dp[u][j] = N + 1 ;
for(int v=1;v<=N;v++){
if(map[u][v] == 0) continue ;
dfs(v) ;
for(int j=P;j>=1;j--){
dp[u][j]++ ; //在孩子v中取0个结点,也就是说将孩子v连接的边去除,因此这里需要加1
for(int k=1;j-k>=1;k++){
dp[u][j] = MIN(dp[u][j] , dp[u][j-k]+dp[v][k] ) ;
}
}
}
}
int main(){
int a ,b ;
while(scanf("%d %d",&N,&P) == 2){
memset(map , 0 ,sizeof(map) );
memset(vis , 0 ,sizeof(vis) );
for(int i=1;i<N;i++){
scanf("%d %d",&a,&b);
map[a][b] = 1 ;
vis[b] = 1 ;
}
for(int i=1;i<=N;i++){
if(!vis[i]){
root = i ;
}
}
dfs(root) ;
_min = N + 1 ;
for(int i=1;i<=N;i++){
if(i == root){
_min = MIN(_min ,dp[i][P]);
}
else{
_min = MIN(_min , dp[i][P]+1); //最后的最优子树不一定以原来的根为根
}
}
printf("%d\n",_min);
}
return 0;
}