赞
踩
版权声明:本文为博主原创文章,未经博主允许不得转载。
之所以这样命名树规,是因为树形DP的这一特殊性:没有环,dfs是不会重复,而且具有明显而又严格的层数关系。利用这一特性,我们可以很清晰地根据题目写出一个在树(型结构)上的记忆化搜索的程序。而深搜的特点,就是“不撞南墙不回头”。这一点在之后的文章中会详细的介绍。
首先是扫盲,介绍几条名词的专业解释以显示我的高端(大部分人可以略过,因为学习到树规的人一下应该都懂……):废话说完了,下面是正文:
拿到一道树规题,我们有以下3个步骤需要执行:
判断是否是一道树规题:
即判断数据结构是否是一棵树,然后是否符合动态规划的要求。如果是,那么执行以下步骤,如果不是,那么换台。
建树:通过数据量和题目要求,选择合适的树的存储方式。
如果节点数小于5000,那么我们可以用邻接矩阵存储,如果更大可以用邻接表来存储(注意边要开到2*n,因为是双向的。这是血与泪的教训)。如果是二叉树或者是需要多叉转二叉,那么我们可以用两个一维数组brother[],child[]来存储(这一点下面会仔细数的)。
写出树规方程:通过观察孩子和父亲之间的关系建立方程。我们通常认为,树形DP的写法有两种:6、软件安装:判断环+缩点+多叉转二叉;
【4、5、6属于依赖问题的变形】
基本的知识掌握和步骤了,我们就通过习题来感受一下树规的魅力,先来看这样一道题:第2行:n个用空格隔开的整数,为每个节点的分数(分数<100)。
【输出格式】
第1行:一个整数,为最高加分(结果不会超过4,000,000,000)。看到这个问题,我们首先应该想到的是这道题是否属于动态规划,而这里我们发现,结合问题,如果整棵树的权值最大,必然有左子树的权值最大,右子树的权值也最大,符合最优性原理。所以是动态规划。
而却不是一道树规的题目。因为我们可以用区间动规的模型解决掉:直接定义一个f[i][j]表示从i到j的最大值,则f[i][j]=max(f[i][k-1]*f[k+1][j]+a[k]),枚举k即可。接下来是如何建树的问题,只有把树建好了,才能输出其前序遍历。于是,我们看到了两个关键词:二叉树,中序遍历。有了这两个关键词,加上区间动规,这棵树就能建起来了。根据二叉树的特性来建树(这里不再具体讨论树的详细的构造了,中序遍历和前序遍历不懂得自己百度)。所以这颗树的前序遍历,只需要边动规边记录下root[i][j]=k表示i到j的根为k即可确定树的构造。
再执行第二步:我们观察到题目数据量不大,所以有两种选择:邻接矩阵和邻接表。因为邻接矩阵的代码简单,思路清晰,所以建议能写邻接矩阵的时候就不要写邻接表了。我们设ma[x][y]为边的值,因为树是双向的,所以要再记录ma[y][x]。
设tree[v,1]为节点v的左子树,tree[v,2]为节点v的右子树,然后我们再递归建树(因为树是递归定义的,所以很多时候建树都要考虑递归)。
建树的问题解决的了,我们就要列状态转移方程了。根据求什么设什么的原则,我们定义f[i][j]表示以i为节点的根保留k条边的最大值,那么f[v][k]=max(f[v][k],(f[tree[v][1]][i]+f[tree[v][2]][k-i-1]+num[v])),我们枚举i就可以了。正如我开头提到的。因为树是递归定义的所以我们可以用记忆化搜索的形式(dfs)来具体实现。而树本身严格分层,而且没有环。所以是不会重复的。
F[1][Q+1]就是答案。因为题目中给的是边的权值,而我们在处理时将每条边的权值全赋给其所连的父节点和子节点中的子节点(将关于边的问题转化为关于点的问题),所以最后是Q+1,表示点的数目。
分两种情况:取(1)和不取(0)。
然后,我们继续利用已经求得的f[i][j]的值来思考如何找到路径:
首先定义一个path()函数。如果f[i][j]=f[b[i]][j],那么节点i必然没有取,让ans[i]=0;否则,节点i一定取到了。(为什么呢?其实,这是依照第一问的dfs来思考的,第一问的dfs是这样定义的,所以我们就这样考虑了。)然后依照上一问,if(f[x][y]==f[b[x]][k-1]+f[c[x]][y-k]+s[x]),那么我们在i节点后选的一定是以上的方案,在这时让ans[i]=1,继续深搜path()即可。最后从1到n依次输出取到的点即可。
输出:
5
【算法&思路】:同样,这道题目类似与第4题,是一个依赖的问题,毫无疑问是一道动态规划,但是它确实是树规么?我们来想这样一组数据,1依赖2,2依赖3,3依赖1。这样符合题目要求,但有形成了环,所以不是一棵树了。但是根据题目,这样特殊的情况,要么全要,要么全就不要。所以,事实上我们可以将这个环看成一个点再来动规,即缩点。如何判断是否是一个环呢,依照数据范围,我们想到了floyed(弗洛里德),这是在这种数据范围内性价比最高的方式。最后树规。于是一个比较清晰的步骤就出来了:判环,缩点,树规。
接下来是细节:首先存树,毫无疑问,是邻接矩阵。
做floyed。如果两点之间mapp[i][j]中有另一条路径相连,即mapp[i][k]=1 && mapp[k][j]=1(1表示两点是通的);那么mapp[i][j]也是通的且是环。
缩点。这个是最麻烦的,麻烦在于我们要把缩的点当成一个新点来判断,而且要判断某个点是否在某个环里。我们用染色法来判断,用所占的空间w控制颜色的对应,有以下三种情况:
1、点i所在的环之前没有判断过,是新环。那么,我们将这个新环放到数组最后,即新加一个点,然后让这两个点的空间标记为负值tmpw,且tmpw+tmpn(新点的下标)等于原来的点数,这样,我们就可以通过某个点的空间迅速找到他所在的新点。像钥匙一样一一对应;
2、点i所在的环之前已经判断过了,是旧环(已合成新点),且i是环的一部分。那么我们就把i也加到这个新点里面,即体积,价值相加即可;
3、点j所在的环是旧环,但是i不是环的一部分(例如1依赖2,2依赖3,3依赖1。4也依赖1,那么,4所在的是个环,但4不属于环的一部分)。那么,把j的父亲转到新点上d[j]= n-w[d[j]]。
以上缩点的工作做完之后,剩下的就是一棵树。就可以在这上面动规了:先将其转换成一棵左孩子右兄弟的二叉树,之后记忆化。i的孩子不取f[b[x]][k]=dfs(b[x],k);还是取:
f[c[x]][y-i]=dfs(c[x],y-i);
f[b[x]][i]=dfs(b[x],i);
f[x][k]=max(f[x][k],v[x]+f[c[x]][y-i]+f[b[x]][i]);
最后答案是f[c[0]][m]。
问题描述
有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?
输入格式
第一行包含一个整数 n 。
接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。
接下来一共 n-1 行,每行描述树上的一条边。
输出格式
输出一个整数,代表选出的点的权值和的最大值。
样例输入
5
1 2 3 4 5
1 2
1 3
2 4
2 5
样例输出
12
样例说明
选择3、4、5号点,权值和为 3+4+5 = 12 。
数据规模与约定
对于20%的数据, n <= 20。
对于50%的数据, n <= 1000。
对于100%的数据, n <= 100000。
权值均为不超过1000的正整数。
解题过程
刚学习完树形动态规划的原理,所以乍一看就知道此题应该用树形动态规划解决。分两步:1、建树。2、动态规划。
刚开始选择的存储结构是二维数组,既每一行表示树的一层,每一列表示该层(行)的所有节点;记录下树的最大层数,从最后一层开始改变每个节点的状态,最后从根节点中获取最优解。
这种存储结构利用了每个节点(除根节点以外)只有唯一双亲的性质。PARENT(T,x)操作可以在常数时间内实现。反复调用PARENT操作,直到遇见无双亲的节点时,便找到了树的根,这个就是ROOT(x)的过程。但是,在这种表示法中,求节点的孩子时需要遍历整个结构。
《2》、孩子表示法
这里主要给出一种类似于邻接表的表示法。把每个节点的孩子节点排列起来,看成是一个线性表,且以单链表作为存储结构,则n个节点有n个孩子链表(叶子节点的孩子链表为空表)。而n个头指针又组成一个线性表,为了便于查找,可采用顺序存储结构。这种存储结构可形式地说明如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。