一、如何拥有一棵树 how to build a tree
存树的方法有很多种,我个人常用的有:
①链式前向星存图法
因为树也是一种图,所以说对于存图法是适用的。缺点就是不好利用树的性质。
因为比较普遍,教程也很多,就不再赘述了。
②左儿子右兄弟法
1 struct node //结构体存树 2 { 3 int lson,rson;//因为转变为二叉树,所以只有两个儿子 4 }a[N];//这个好像叫tree更标准 5 6 void findplace(int x,int y)//x是根,y是要插入的儿子 7 { 8 int pos=a[x].lson;//这是在x已经有第一个儿子的情况 9 while(a[pos].rson!=-1)//循环找直到右儿子(兄弟位)空出 10 pos=a[pos].rson; 11 a[pos].rson=y;//成功存入 12 } 13 14 int main() 15 { 16 for(int i=0;i<n;i++)//初始化 17 { 18 a[i].lson=a[i].rson=-1; 19 } 20 for(int i=1;i<=n;i++) 21 { 22 cin>>x>>y>>w; 23 a[y].val=w; 24 if(a[x].lson==-1)//x还没有儿子就可以充任 25 y=a[x].lson; 26 else 27 findplace(x,y);//否则就适用上面的函数了 28 } 29 }
优点是可以非常方便地遍历整个树,缺点是有点慢。但是在操作系统内部,文件夹之间似乎也是这么存放的呢。
遍历的方法:
void printree(int pos) { cout<<pos<<endl; int r=a[pos].rson,l=a[pos].lson; while(r!=-1)//先打印兄弟 { printree(r); r=a[r].rson; } if(l!=-1)//再考虑儿子 printree(l); }
二、如何状态转移 how to dp
树上的dp一般状态转移都是和子树相关的,而且第一维一般而言都是当前节点的标号。利用记忆化搜索其实能更好利用树,刷表填表我都不太适应……
普通dp比较常用的考虑方法是f[i][0/1]表示点i选/不选的情况下,其子树的最优情况;
如果是树形背包,那么f[i][j]表示在以点i为根节点的子树中,选择j个点的最优情况。
当然这个要视具体情况分析而定。
一般而言要考虑的问题:叶子节点(也就是边界)、选择和不选择时对应的子节点情况(有什么要求)、根节点怎么处理等。如果没有根节点,可以考虑构建一个虚根;如果题目中是边权,可以考虑下沉至点权来做。注意根节点是不是占用名额!这个是比较重要的。
三、如何水经验 how to get exp
luogu上P201x的题目很多都是树形dp的。个人的成长路线是:
1.没有上司的舞会(经典dp)
2.二叉苹果树(树形背包入门)
3.选课(树上依赖背包入门)
4.战略游戏(普通dp)
5.有线电视网(比较有意思)
本蒟蒻比较菜,所以底线就到这里啦。