树形DP

来源:互联网 发布:永恒之柱 知乎 编辑:程序博客网 时间:2024/05/16 10:00

DP有些求骗的东西,比如树形DP。
怎么说呢,树也是个DAG,按理说,是DAG就能动规。


  • 用题说事
    • 先看一道题
    • 再来一道题
    • 给你三个题
  • 几个知识点待补充
    • 树的最大独立集
    • 树的重心质心
    • 树的直径树的最长路径最远点对
    • OBST最优排序二叉树
  • 总结一下


用题说事

先看一道题

洛谷P1271 聚会的快乐

得自己建树,人名字符串还要映射……板恶心……

说正事。
这道题里,上司—下属关系就是一个天然的有向边,利用它,从老板开始,往下深搜就好啦~

Created with Raphaël 2.1.0开始建树—————————————决策——————————————决策一:不取现在搜到的人决策二:取现在搜到的人,当他的上司不取才能进行结束

建树当然是好用的链式前向星啦~不过转二叉树也可以
真正写代码时,是看取不取他的下属
一个下属取,一个下属不取……
在加上本身取不取就是这一棵子树最大值。

代码 75分

再来一道题

洛谷P1271 有线电视网

分析下次上

代码 AC

给你三个题

似乎不止三个……
洛谷 P2014 选课
洛谷 P2015 二叉苹果树
(紫书p282)
UVa 12186 Another Crisis
UVa 1220 Party at Hali-Bula
UVa 1218 Perfect Service
题解会有的……


几个知识点(待补充)

来自紫书、蓝书、挑战程序设计竞赛

树的最大独立集

对于一棵 n 个结点的无根树,选出尽量多的结点,使其中任意两个结点均不相邻(即最大独立集)。
洛谷P1271就是一个最大独立集问题,不过带了权(幽默系数)。然而,P1271是有根的(上司)。

那么,对于最大独立集问题,只需随便“提溜”一个结点“当”根,然后就很开心了。
紫书p281
(紫书p281)
对结点i,只有两种决策:选和不选。即方程:

d(i)=max{1+jgrandson(i)d(j),json(i)d(j)}

d(i)表示以 i 为根结点的子树的最大独立集

写代码时,方法一是记搜(如P1271);方法二是“刷表法”:对结点 i ,计算出d(i)后,更新 i 的父亲和祖父的累加值,这样只需存每个结点的父亲就好(只开一个数组)。

法一代码

    int dp[N][2];    int dfs(int i,int qu){        if(dp[i][qu])return dp[i][qu];        int ans=0;        for(int e=head[i];e;e=nex[e]){            int son=dfs(to[e],0);            if(!qu)son=max(son,dfs(to[e],1));            ans+=son;            }        return dp[i][qu]=ans+son*qu;        }

法二代码

树的重心(质心)

紫书p281

对于一棵 n 个结点的无根树(又是无根树),找到一个点,使把树变成以它为根的有根树时,最大子树的节点数最小。也就是说,删除它后最大的树的节点数最少。

同样,先“提溜”一个结点“当”根,设d(i)表示以 i 为根的子树中结点个数,则有:

d(i)=1+json(i)d(j)

然后随便dfs就行了。
这只是把d(i)求出来了,删除 i 时,在
f(i)=max{maxjson(i)d(j),nd(i)}

中,取一个最小的f(i),此时 i 就是重心。
紫书p281
(紫书p281)
f(i)表示删除 i 后,最大子树的结点数。n-d(i)表示 i 的父结点连的结点总数。因为d(i)=1+json(i)d(j),d(i)就是图中虚线方块外的结点数,自然n-d(i)就是虚线方块内的结点数。

代码

int f[N],d[N];int dfs(int i,int fa){    int sum=0;    for(int e=head[i];e;e=nex[e])        if(to[e]!=fa)sum+=dfs(to[e],i);    return d[i]=sum+1;    }int zhongxin(int root){    int a=dfs(root,-1),ans=-1,ff=INF;    for(int i=1;i<=n;i++){        int son=n-d[i];        for(int e=head[i];e;e=nex[e])            son=max(son,d[to[e]]);        if(son<ff){            ff=son;            ans=i;            }        }    return ans;    }

树的直径(树的最长路径、最远点对)

紫书p281、挑战程序设计竞赛p295

对 n 个结点的无根树,找到一条最长路径,即找到两个点,使它们距离最远。

普通算法就是随便提溜一个根root,然后dfs找到根最远的点,再从这个点出发,dfs找最远的点。这样,第二次dfs找的路径就是树的直径。
一眼看就是对的。可为什么对,不说了,不重要。代码很简单,不打了,挑战程序设计竞赛p295有。

重要的是DP算法。
紫书p281
(紫书p281)
dp只能求最长路长度,如果只要求长度,那就开心了。
设d(i)表示以 i 为根的子树中根到叶子的最大距离,则有:

d(i)=1+maxjson(i){d(j)}

其中”1”就是 i 到 j 的路径长度,也可换成权值。然后就好理解了。
最后结果就是root的子节点中,d最大的两个d(u),d(v),再加上2。(d(u)+d(i)+2)。
看上面的图:d(u),d(v)已知,u到灰色节点距离为1,v到灰色节点距离也为1,加起来就是d(u)+d(i)+2(也可带权)。

int d[N];int dfs(int i,int fa){    int ans=0;    for(int e=head[u];e;e=nex[e])        if(to[e]!=fa)            ans+=dfs(to[e],i);    return d[i]=ans+1;    }

OBST(最优排序二叉树)

蓝书p63
有 n 个符号,每个符号有一个检索频率,互不相同。将它们建成一棵排序二叉树,使总检索次数(所有符号的频率与其深度乘积之和,根的深度==1)最小。
UVa 10304 Optimal Binary Search Tree

这算序列型DP还是树状DP呢?不管,反正这个挺难的。
前方高能!!!
和XXX一样,先选根,再递归左右子树。
根的频率知道了,那么,它的儿子就要乘2,孙子就要乘3……太麻烦!直接所有的都加一次得了。子树?子树再当成根,递归。那方程就是:

d(i,j)=sum(i,j)+maxi<=k<=j{d(i,k1)+d(k+1,j)}

d(i,j)表示序列i~j的总检索次数,sum(i,j)表示i~j频率和。O(n3)

int sum[N],d[N][N];int build(int l,int r){    if(l==r)return sum[l]-sum[l-1];    if(d[l][r])return d[l][r];    int son=0;    for(int k=l;k<=r;k++)        son=max(son,build(l,k-1)+build(k+1,r));    return son+(sum[r]-sum[l-1]);    }int work(){    for(int i=1;i<=n;i++)        sum[i]+=sum[i-1];//直接在输入数组上做前缀和    return build(1,n);    }

这只是比较朴素的方法……
前方真正高能!!!
能降至O(n2)。记K(i,j)为让d(i,j)取最小值的决策k,有

K(i,j)K(i,j+1)K(j+1,i+1)

证明要四边形不等式!!!


如果对于任意的a1≤a2< b1≤b2,有不等式

m(a1,b1)+m(a2,b2)m(a1,b2)+m(a2,b1)
成立,则m(i,j)满足四边形不等式。

由上面所设,i≤j,所以套不等式:

K(i,i)+K(i+1,j+1)K(i,j+1)+K(i+1,j)

i≤j,而i+1就不一定了,于是K(i+1,j)无意义,扔掉。
就是:
K(i,i)+K(i+1,j+1)K(i,j+1)


所以,枚举k就可改成K(i,j-1)~K(i+1,j)。这两个是早就算过的,从而降至O(n2)

总结一下

原创粉丝点击