POJ 1947 Rebuilding Roads(树DP)
题意:给一棵树,问最少砍断多少条边才能产生一个恰含m个结点的子树。
分析:将树有根化后,我们考虑以 i 为根结点的子树,要在这棵子树内产生一个结点数目为 j 的子树,要么包含结点 i ,要么不包含,所以定义 dp[i][j][0]表示从以结点 i 为根的子树中得到恰含 j 个节点且不含结点 i 的子树最少需要砍掉的边数,dp[i][j][1]表示从以结点 i 为根的子树中得到恰含 j 个节点且包含结点 i 的子树最少需要砍掉的边数。
初始化:树DP中的初始化其实可以把所有结点都看成是叶子结点来处理,dp[i][0][0]=0,dp[i][1][1]=0,其余的全初始化为INF
状态转移:dp[a][j][0]的转移比较简单,dp[a][j][0]=min(dp[a][j][0],min(dp[b][j][0],dp[b][j][1]+1)),b是a的儿子结点;dp[a][j][1]的转移就是一个类似分组背包的过程,dp[a][j][1]=min(dp[a][j][1],dp[a][j-k][1]+dp[b][k][1]),其中0=<k+1<=j;
View Code
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; #define N 151 int n,m,e; int first[N],next[N],v[N]; int dp[N][N][2]; void init() { e=0; memset(first,-1,sizeof(first)); } void add(int a,int b) { v[e]=b; next[e]=first[a]; first[a]=e++; } void dfs(int a) { memset(dp[a],0x3f,sizeof(dp[0])); dp[a][0][0]=0; dp[a][1][1]=0; int i,b; for(i=first[a];~i;i=next[i]) { b=v[i]; dfs(b); for(int j=m;j;j--) { dp[a][j][0]=min(dp[a][j][0],min(dp[b][j][0],dp[b][j][1]+1)); dp[a][j][1]++; for(int k=1;k+1<=j;k++) { dp[a][j][1]=min(dp[a][j][1],dp[a][j-k][1]+dp[b][k][1]); } } } } int main() { int a,b; while(~scanf("%d%d",&n,&m)) { init(); for(int i=1;i<n;i++) { scanf("%d%d",&a,&b); add(a,b); } dfs(1); printf("%d\n",min(dp[1][m][0],dp[1][m][1])); } return 0; }