poj 1947 Rebuilding Roads(树形dp)
树形dp,顾名思义,就是在“树”的结构下用动态规划求最值。既然是在“树”形的结构下进行的,那么做这类题的第一步是先建树,然后顺着树的结构进行dp,一般从两个方向进行:
1. 根—>叶:不过这种动态规划在实际的问题中运用的不多,也没有比较明显的例题。
2. 叶->根:既根的子节点传递有用的信息给根,完后根得出最优解的过程。
至于状态转移公式,这要具体题目具体分析。呃,来说说这道题。
题意:各处含有N个节点的树,问你最少剪去几个边可以得到一颗包含P个节点的子树。
思路:dp[i][j]表示以i为根的子树包含j个节点最少剪去的边,若j为1的话dp[i][1] 就等于i的子节点数,然后就是0-1背包的问题了。还是看代码吧;
View Code
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <iostream> #include <algorithm> #include <vector> #include <map> #include <math.h> #define N 154 #define INF 0xffff using namespace std ; vector<int>node[N] ; int dp[N][N] , p[N] ; int n , m ; int min( int x , int y ) { return x < y ? x : y ; } int dfs( int root ) { int i , j , k ; //从叶子节点开始 for ( i = 0 ; i < node[root].size() ; i++ ) { dfs( node[root][i] ); } dp[root][1] = node[root].size();//初始化每个节点为它子节点的数 for ( i = 0 ; i < node[root].size() ; i++ ) { for ( j = m - 1; j >= 0 ; j-- ) if ( dp[root][j] != INF ) for ( k = 1 ; k <= m - j ; k++ ) if ( dp[node[root][i]][k] != INF )//进行背包运算,dp[root][j] + dp[node[root][i]][k] 是 //求以root为根的子树包含j+k个子节点要剪去的最少边, //-1是减去root与node[root][i] 之间的连线 dp[root][j+k] = min ( dp[root][j] + dp[node[root][i]][k] - 1 , dp[root][j+k] ); } return dp[root][m] ; } int main() { int i , j , minx ; while ( scanf( "%d%d" , &n , &m ) != EOF ) { for ( i = 0 ; i <= n ; i++ ) for ( j = 0 ; j <= m ; j++ ) dp[i][j] = INF ; memset( p , 0 , sizeof ( p )); for ( i = 1 ; i < n ; i++ ) { int x , y ; scanf( "%d%d", &x , &y ); node[x].push_back( y ); p[y] = x ; } int root = 1 ; while ( p[root] != 0 ) root = p[root] ; minx = dfs( root ); for ( i = 1 ; i <= n ; i++ ) if ( i != root && dp[i][m] < minx ) minx = dp[i][m] + 1; cout<<minx<<endl; } return 0 ; }