DP毕竟是算法中最精妙的部分,理解并玩得花哨还是需要一定的时间积累
之前对普通的DP也不敢说掌握,只能说略懂皮毛
在学习树形DP 的同时也算是对DP有了更深的理解吧
DP的关键就在于状态的定义以及找转移
首先要考虑清楚状态,状态要能够很好地并且完整地描述子问题
其次考虑最底层的状态,这些状态一般是最简单的情况或者是边界情况
再就是考虑某一个状态能从哪些子状态转移过来,同时还要考虑转移的顺序,确保子问题已经解决
树形DP很多时候就是通过子节点推父亲节点的状态
还是通过题目来加强理解吧
1.HDU 1520
题意:给一棵树,选最多的结点使得选择的结点不存在直接的父子关系
很容易想到一个结点有两个状态:选或者不选
所以自然地想到状态dp[u][0/1]表示u子树内的最佳答案,u的状态为选或者不选
初始化自然是叶子结点dp[u][0]=0,dp[u][1]=w[u]
转移则可以考虑依次考虑
u不选的时候:u的儿子可以任意选或者不选,所以dp[u][0]+=max(dp[v][0],dp[v][1])
u选的时候:u的儿子必定不能选,所以dp[u][1]+=dp[v][0] 然后dp[u][1]+=w[u]表示加上u这个点
答案自然就是max(dp[rt][0],dp[rt][1])了
#include"cstdio" #include"queue" #include"cmath" #include"stack" #include"iostream" #include"algorithm" #include"cstring" #include"queue" #include"map" #include"set" #include"vector" #include"bitset" #define LL long long #define ull unsigned long long #define mems(a,b) memset(a,b,sizeof(a)) #define mdzz int mid=(L+R)>>1 #pragma comment(linker, "/STACK:1024000000,1024000000") using namespace std; const int N = 6005; const int M = 1e5+5; const int MOD = 998244353; const int INF = 0x3f3f3f3f; int tot; int first[N],w[N],deg[N]; int dp[N][2]; struct node{ int e,next; node(){} node(int a,int b):e(a),next(b){} }edge[M]; void init(){ tot=0; mems(first,-1); mems(deg,0); mems(dp,0); } void addedge(int u,int v){ edge[tot]=node(v,first[u]); first[u]=tot++; } void dfs(int u){ dp[u][0]=0; dp[u][1]=w[u]; for(int i=first[u];i!=-1;i=edge[i].next){ int v=edge[i].e; dfs(v); dp[u][0]+=max(dp[v][1],dp[v][0]); dp[u][1]+=dp[v][0]; } } int n; int main(){ //freopen("in.txt","r",stdin); while(~scanf("%d",&n)){ init(); for(int i=1;i<=n;i++) scanf("%d",&w[i]); int u,v; while(1){ scanf("%d%d",&v,&u); if(!v&&!u) break; addedge(u,v);deg[v]++; } int rt; for(int i=1;i<=n;i++) if(!deg[i]){ dfs(rt=i); break; } printf("%d\n",max(dp[rt][0],dp[rt][1])); } return 0; }