DP有些求骗的东西,比如树形DP。
怎么说呢,树也是个DAG,按理说,是DAG就能动规。
用题说事
先看一道题
得自己建树,人名字符串还要映射……板恶心……
说正事。
这道题里,上司—下属关系就是一个天然的有向边,利用它,从老板开始,往下深搜就好啦~
建树当然是好用的链式前向星啦~不过转二叉树也可以
真正写代码时,是看取不取他的下属
一个下属取,一个下属不取……
在加上本身取不取就是这一棵子树最大值。
再来一道题
分析下次上
给你三个题
似乎不止三个……
洛谷 P2014 选课
洛谷 P2015 二叉苹果树
(紫书p282)
UVa 12186 Another Crisis
UVa 1220 Party at Hali-Bula
UVa 1218 Perfect Service
题解会有的……
几个知识点(待补充)
来自紫书、蓝书、挑战程序设计竞赛
树的最大独立集
对于一棵 n 个结点的无根树,选出尽量多的结点,使其中任意两个结点均不相邻(即最大独立集)。
洛谷P1271就是一个最大独立集问题,不过带了权(幽默系数)。然而,P1271是有根的(上司)。
那么,对于最大独立集问题,只需随便“提溜”一个结点“当”根,然后就很开心了。
(紫书p281)
对结点i,只有两种决策:选和不选。即方程:
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 为根的子树中结点个数,则有:
然后随便dfs就行了。
这只是把d(i)求出来了,删除 i 时,在
中,取一个最小的f(i),此时 i 就是重心。
(紫书p281)
f(i)表示删除 i 后,最大子树的结点数。n-d(i)表示 i 的父结点连的结点总数。因为 d(i)=1+∑j∈son(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)
dp只能求最长路长度,如果只要求长度,那就开心了。
设d(i)表示以 i 为根的子树中根到叶子的最大距离,则有:
其中”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)表示序列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,有
证明要四边形不等式!!!
如果对于任意的a1≤a2< b1≤b2,有不等式
由上面所设,i≤j,所以套不等式:
i≤j,而i+1就不一定了,于是K(i+1,j)无意义,扔掉。
就是:
所以,枚举k就可改成K(i,j-1)~K(i+1,j)。这两个是早就算过的,从而降至 O(n2) 。