嗯博主作为一个蒟蒻半年忘了验证手机然后……就登不上号了QAQ
趁大休回去博主验证完了可算是能登录了orz
真相:督促博主重新写博客的真正原因是博主找不到放松心情的方法了(最近博主不敢颓废)
各单位注意,前方博主口胡高能预警
树形DP,比线性DP稍微难搞一些,但事实上非常好理解。树拥有一堆能拿出来DP的优秀性质,比如子结构啊(子树)之类的,非常适合出DP题啊(无雾)
既然本来树上的每个节点都有子结构了,那就可以通过两种方式DP
1、从父亲节点推向儿子节点(根–>叶)
2、从儿子节点推向父亲节点(叶–>根)
怎么推呢?当然是利用递归啦,可以从根推向叶子,然后叶子还可以回溯更新根节点数据
当然根据具体的题得抉择一下是弄双向边还是单向边,如果只从叶节点上的数据更新向根节点就能解决问题,省空间的单向边就可以啦~
根据题目要求,可能需要将多叉树转成二叉树再DP,这个得需要注意一下。
那接下来来一道题好了 Luogu P1352
这道题的要求是如果一个节点的父节点被选中,则它不能被选,那么很容易就能推出来递推的式子(至少博主这个DP白痴级的都能推出来),大概就是下面这种情况(r[x]表示该节点被选中时可获得的权值, t[x]表示x被选的情况下其子树最大总和,f[x]则表示x不被选的情况下其子树最大总和,son[x][i]表示x的第i个儿子节点,num[x.son]表示x的儿子节点个数,子树包括自身):
t[x]=r[x];f[x]=0;
for(i=1;i<=num[x.son];i++){
t[x]+=f[son[x][i]];
f[x]+=max(t[son[x][i],f[son[x][i]);
}
那么我们就只需要从根节点一路搜索到叶节点再更新数据就好了,建树的时候甚至只用建单向边(输入时已经说明了谁是谁的上司,通过入度还能找个根);需要注意输入有可能有负数,用快读的要小心一些。代码如下(码丑勿喷):
#include<cstdio>
using namespace std;
const int N=6005;
int n,r[N],h,t[N],f[N],rt[N];
int nxt[N],fro[N],to[N];
int getnum(){int num=0,b=1;char c=getchar();
while(c<'0'&&c!='-')c=getchar();if(c=='-')b=-1,c=getchar();
while(c>='0')num=(num<<3)+(num<<1)-'0'+c,c=getchar();
return num*b;
}
inline void edd(const int &u,const int &v){
nxt[++h]=fro[u],fro[u]=h,to[h]=v;rt[v]++;
}
inline int max(const int &a,const int &b){
if(a>b)return a;return b;
}
void src(int now){
t[now]=r[now];f[now]=0;
for(int i=fro[now];i;i=nxt[i]){
src(to[i]);
t[now]+=f[to[i]];
f[now]+=max(t[to[i]],f[to[i]]);
}
}
int main(){register int i,l,k,root;n=getnum();
for(i=1;i<=n;++i)r[i]=getnum();
for(i=1;i<n;++i){l=getnum(),k=getnum();edd(k,l);}
for(i=1;i<=n;++i)
if(rt[i]==0){root=i;break;}
src(root);
return printf("%d\n",max(t[root],f[root])),0;
}
总之这是树形DP的一道入门级题目,树形DP更难的题目还多的是,所以想在比赛里碰到树形DP不一脸懵逼还是需要多刷些题的(但容易刷吐= =)。