动态规划-树形dp总结

来源:互联网 发布:js array map index 编辑:程序博客网 时间:2024/05/16 01:35

一.简单的从下到上和从上到下的统计

1.      dp[u]表示以u为根的一共有多少个节点.可以用来求重心.

2.      每个点出发能够走得最远的长度.dpm[u], dps[u]用来保存u为根到子树的最长距离,注意两者区别是不同的儿子(不想交路径).另外有一个f[u]指的是去掉u的儿子们之后,u可以发出的最长链.这样的是一个规范的从上往下的dp.我是这样定的:定状态f[u]要求不能考虑u的儿子,具体考虑不考虑u看题意.比如这个就要看u.那么我们的主要过程是已知u去推v.那么遍历u的儿子v,穿进去v的答案应该是1+非v的最长子树链和1+u的答案.

3.  把一棵树变成环.其实就是把一棵树先变成一个链,然后再加一条边变成环.而且变成环也好变.考虑u和v们,v首先需要变成链,但是每条链都有两个机会不用拆,并且要求v在v的链中也是链段.这样的话dp二维01,0带便随意,1代表强制根节点是一个链段.之后横着dp.

flag = 1;

            f_2 = min(f_2 + dp[v][0] +2, f_1+dp[v][1]);//v是不是一个//链端

            if(f_2 > INF)

                f_2 = INF;

            f_1 = min(f_1 + dp[v][0] +2, dp[v][1]+cnt*2+sum);//v//不是一个链端

            cnt++;

            sum += dp[v][0];

 

多开一维来描述根的某种状态的方法很常用.本题是是否为链的一端.

(2)还有一题:不能选的点之间有相连的地方.开一维定义是否选根了.

(3)另外cf也有一道题,要求一颗子树上有且仅有一个黑色的点.那么多开一维来看这个u为根的子树是否有黑色的点.这样从v和到u的时候就能够决定是否是要不要切断这条边.

(4)1点往下面走,问在走的距离小于limit的时候最多能够到达多少个不同的顶点.往下走要看回来不回来,因为回来才能够继续往下走,因此再开一维来描述是否回到了根节点.此外本题有一个小转换,距离值一般太大,我们dp中写的是走的点数,然后距离值作为dp的值.最后扫一遍出答案.

(5)一棵树,上面都是村庄,水流只能够往父亲节点的方向流,题目强制1大根,并且1点有一个处理厂.一共可以建立k个处理厂.问最小的运费和.一共有100个点.要求每个点都有一个负责点,并且它会往距离自己最近的负责点流.怎么做?dpu的时候如果u不建的时候v的父亲是谁?发现题目中说:只能往父亲节点流.那么很简单,我们开dp[u][fuze][k]表示现在是fuze这个点来负责u,那么如果u的儿子们没有新建负责站,就只能是fuze来负责他们了.这样就能够算了.开个数组dp[][][],然后就是背包.dfs指明方向.

(6)  (5)题表面上一样,但是题意重点完全不同:每个点会往自己最近的负责站走,并且可以随便走,没有儿子到父亲的指向性了.每个点都有一个d的限制,要求它走的距离<=d就能与带一个负责站.每个点建负责站都有自己的花费.怎么做?其实会往最近的没有什么用,这个条件.我们扩充成:i依赖j的意思是j点建立了负责站使得i可以在d内满足条件.之后怎么解决兄弟之间的影响呢?有一个限制:如果在最优答案中,fa选的是x(x可能是任意一个点),那么他的儿子v如果不在v的内部解决,会走一下fa,那么一定之后会走fa的那个依赖节点.这样走一定不走出最好的情况.因为既然v走过fa之后有一个更好的y来依赖,为什么fa不去依赖y?所以我们可以把这个统一了.在写的时候,dp[i][j]就是i以来的j,那么i的儿子们要么自己解决,要么调用dp[v][j].每次的i全部都枚举1~nj.

 

4.      算树上有几个三个点组成的复杂路.反向思维转换,找出所有的简单路径.只要是三个点并且要求有一个简单路径,那么就是枚举中间点是谁就好,然后另外两个分别在两个儿子上,或者父亲,如果另外一个在一个儿子或者父亲上,一定会不能画.并且由于这样枚举的一定是中间的,因此一定没有重复.用dp来记录以u为根的节点数.然后需要横着dp过去.

 

5.      计算一棵树上,去掉一条边之后剩下的两棵树分别的直径.首先,求u为根子树的直径很好求:先搞出来距离dpm和dps,然后再dfs之后就搞出了直径.第二:是重点.就像2说的,要去掉u,并且要考虑u的兄弟,因此我们不能定fa为f[fa],因为这样过答案f要包含fa的一些儿子,违反了2中的规则.于是定f[u],含义是去掉u本身及u的儿子之后的树的最长直径.那么下面就是如何从u推到v.看v的答案组成,包含u的答案,之后是u的非v的儿子们的最长直径,之后是和u相关的,就是u向上的最长链和u非v的两条最长链挑出两个组成.u向上的最长链经常搞.这样就搞出来了.因此需要向下的3条链,儿子中2个最大的直径.下面的代码因为只存了1个u为根直径,因此每次都要再遍历一次找出儿子的2个最大的直径….

if(fa !=0)//第一次不用算结果

    {//这是算结果用的,因为u的f定义已经得到答案了,因此可以算答案了.

        int t = max(u_ans, dia[u])*p[ans_id].w;

        if(t < ans)

        {

            ans = t;

            ans_i = ans_id;

        }

        else if(t == ans)

        {

            ans_i = min(ans_i, ans_id);

        }

    }

    int diam = 0, dias = 0;

    int nouse = 0;

    REP(i, 0, G[u].size())//先遍历一遍,如果已经存过两个直径,就不用遍历了

    {

        int v = G[u][i].to;

        if(v==fa)

            continue;

        gao(diam, dias, nouse, dia[v]);

    }

    //int uforv_l = u_l;

    REP(i, 0, G[u].size())//重点开始

    {

        int v = G[u][i].to;

        int id = G[u][i].no;

        if(v==fa)

            continue;

        int v_ans = u_ans;//v_ans的第一个组成

        if(diam==dia[v])//第二个

        {

            v_ans = max(v_ans, dias);

        }

        else

        {

            v_ans = max(v_ans, diam);

        }

        int a, b;//第三个

        if(dp[u][0]==1+dp[v][0])

        {

            a = dp[u][1];

            b = dp[u][2];

        }

        else if(dp[u][1] == 1+dp[v][0])

        {

            a = dp[u][0];

            b = dp[u][2];

        }

        else

        {

            a = dp[u][0];

            b = dp[u][1];

        }

        v_ans = max(a+b, v_ans);

        v_ans = max(a+u_l, v_ans);

        dfs(v, u, v_ans, 1 + max(a, u_l),id);//v_l很容易就算出来了

}

6.      求出一棵树的直径,并且问有多少个长度为这样的直径.关键是记数.并且整明白不相交路径和不同的路径的区别. 对于u,在统计完子树v内部的直径后,应该是有u参与,伸出来两条链的.关键有两点,一个是u到v的最大长度,并且最大长度下有几个不同的路.第二个关键是要横着做dp,保存一个最长的,并且累加最长的个数,然后看当前的v加上最长的来更新直径.

7.      Bob和alice一起走,轮流选择路.bob尽量往远的,alice尽量往近的走,并且他俩都知道他们走的限制有一个[L, R]的限制.这样其实从上往下的一个贪心,实际上不需要数组的记录记录下来也挺好.但是这道题目的关键是L到r的限制是对的全局的,在u为根的子树上如果我们要的是最大值,我们实际上是在l到r范围内的最大值,因此我们需要dfs的时候,穿进去到u已经有多少的距离了.再去遍历v中找.

8.      用数字染色,要求相邻的数字不能够相同,并且最后每个节点的和加一起的和最小.重点是我们不能只用0和1就染到最好的.多用几种,可能20种. 这样之后,就是多开一维来记录根节点是染成什么.

二.用边来考虑对答案的贡献.

1.      一棵树,随便怎么移动每一个点,只要最后每个点有一个地方,每个地方有一个点.每个点从a到b的长度是点对答案的贡献,求所有最大的贡献度和是多少.一个点跑到哪不好弄,但是换个角度,用边的思维:一条边,在最优答案中一定可以全部都跨过去.你那么这条边的贡献就是2*w*min(两边的点数).转化成了用边对答案的贡献+简单的树形dp

2.      一棵树,选择k个点,使得这些点中两两的距离和最小.要求o(n*k*k).对于一棵树内,给他多少个点,并且自己内部距离和都好确定.但是一旦当成一个整体,和外部的点的关系就不好描述了.我们可以这样:在u为根的子树中,从v转移过来,背包分配给v,那么v的点和外部的交互怎么搞?这样:v的内部的点看做一个整体,变成u这个整体之前,要加上边u->v的贡献值,并且是num[v]和n-num[v]所有的.这样一来就能够dp了.在u为根内的看做一个整体,在从v和到u的时候把u->v边对答案的贡献算出来.

三.先进行一些变换然后再定状态再dp

1.      一棵树,每个点都有一些宝藏,路都有时间消耗.有一个s和一个t,还有时间limit,s点出发要求在时间limit内必须从t出去.在这个要求下问最多的宝藏数.n*limit<=1e6.直接定状态的话:因为需要最终结果走到t处,于是先要找到t所在的一条从s发出的线,在这条线上的是直走一次就好,而不再线上的,必须走下去再返回.这其实可以这样想:s到t的路是一定要跑的,并且绝逼只会跑一次,不会重复跑.那么我第一次先走一边路,并且把宝藏都拿了,时间都改为0.之后就是普通的背包了.这种先走一些贪心的东西可以简化这道题.

2.      要求只能切断limit的边,问在切边花费值<cost的最大limit是多少.注意可以二分limit,之后就是一个必须要选择的树形dp.二分很重要.

3.      枚举分叉点.一个人在x,要到y和到z,走,并且如果x到y更近的话,他会先走到y,然后从y再往z走.路径显然可以重复.在这个图中,x y z都是不确定的可变的.问在这么多种情况中最远的路程是多少.我们一开始会想到枚举y,然后找两条最长的不同的(但是可以相交)长链,但是我们发现不行,这样会不好满足(y近先走y,z近先走z).枚举z和y肯定都不行.那么怎么办呢?我们把图画出来,枚举分叉点!就是独立于x,y,z之后的一个分叉点,从x走到y从y往z走的时候可能会走一段已经走过的路程.那个新的点就是分叉点这样有了分叉点之后我们就可以找出3条最长的连然后按照题目中的来搞就行了.关键是枚举分叉点.

4.      一棵树,但是上面的边有方向.挑一个点出发,能够走遍所有的点,不能走的边要改变方向,有1的代价.让我们挑出两个点,使得代价和最小.在o(n^2)的时间复杂度内就行了.和4.2的一样,只是可以搞2个了.我们先变换一下,为什么会有两个点?我们一定在最终结果中会有一条边不想改变,连接了两个起点.我们只需枚举这条边是谁,然后两边分别o(n)的dp就行了.

四.先找一个根,然后在树上快速转移

1.      一个人要旅游,从树上选择一个节点作为根据地,每次旅游一个地方就要再回来,每个点可能旅游若干次,求出最短的路程以选取的点是什么.先随便找一个点,可以很快的算出.之后发现一个点往另一个点转很好转,u已经算出来的,往v转,只需加加减减u到v这条边的信息就好了.当然也可以用u只去掉u的儿子们的方法从上往下来做,把答案分成了两部分.但是这样做更加简单.

2.      一棵树,但是上面的边有方向.挑一个点出发,能够走遍所有的点,不能走的边要改变方向,有1的代价.让我们挑出一个点,使得代价和最小.先随便找一个根,搞出来.然后很简单的就能在上面转移.(其实也可以用u只去掉u的儿子的方法,之后u->v的来搞,但是这样转移更好写). 

0 0
原创粉丝点击