先写个考试 鸽着~
考试有树 d p dp dp紫题 我回来了
每年联赛基本都有好几道题时树 d p dp dp,这块一定得好好学
U P D : 20201109 UPD:20201109 UPD:20201109期中考砸 回来补博客 q a q qaq qaq 先把例题发上去 后面习题一天补上

一、基本概念

树形动态规划,顾名思义,就是在“树”的数据结构上做动态规划,通过有限次地遍历树,记录相关信息,以求解问题,通常都是建立在图上的。线性的动态规划的顺序有向前向后两种方向,即顺推和逆推;而树形动态规划是建立在树上的,树中的父子关系天然就是个递归(子问题)结构,所以也相应的有两个方向。
①叶 → → →根,即根的子节点传递有用的信息给根,之后由根得出最优解的过程。这种方式 D P DP DP的题目应用比较多。
②根 → → →叶,即需要取所有点作为一次根节点进行求值,此时父节点得到了整棵树的信息,只需要去除这个儿子的 D P DP DP值的影响,然后再转移给这个儿子,这样就能达到根 → → →叶的顺序。
实现方式:树形 D P DP DP是通过记忆化搜索实现的,因此采用的是递归方式。
时间复杂度:树形 D P DP DP的时间复杂度基本上是 O ( n ) O(n) O(n),若有附加维 m m m则是 O ( n m ) O(nm) O(nm)

二、经典问题

1.树的重心

定义 1 : 1: 1:找到一个点,其所有子树中最大的子树结点数最少,那么这个点就是这棵树的重心
定义 2 : 2: 2:以这个点为根,其所有的子树大小都不超过整棵树的一半,那么这个点就是这棵树的重心
S o l u t i o n : Solution: Solution:任选一个结点为根,把无根树变成有根树,然后设 f i f_i fi​表示以 i i i为根的子树的结点的个数,则 f [ i ] = ∑ j ∈ s o n [ i ] + 1 f[i]=\sum\limits_{j∈son[i]}+1 f[i]=j∈son[i]∑​+1。结点 i i i的子树中最大的有 m a x j ∈ s o n [ i ] { f [ j ] } max_{j∈son[i]}\{f[j]\} maxj∈son[i]​{f[j]}个结点, i i i的祖宗有 n − f [ i ] n-f[i] n−f[i]个结点,就能求出删去结点 i i i后的最大连通块了
实现:只需一次 D F S DFS DFS,在无根树转有根树的同时计算即可。
性质 1 : 1: 1:树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么他们的距离和相等。
性质 2 : 2: 2:把一棵树添加或删除一个子叶,树的重心最多只移动一条边的距离。
性质 3 : 3: 3:把两棵树通过一条边相连得到一颗新的树,那么新的树的重心在原来两个树的重心的路径上。

只求树的重心果题还是挺难找的 看这道 p o j poj poj的题吧

poj1655 Balancing Art

题面
题意:求树的重心,并输出删去重心后的最大子树结点数
我注册了个 p o j poj poj账号哈哈, p o j poj poj好像不支持万能库??
裸题 上代码

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define N 20020
#define reg register
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
struct node{int to,nxt;
}edge[N<<1];
int u,v,n,T,ans,cnt,head[N],siz[N],son[N];
inline void addedge(int u, int v){edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline void superadd(int u, int v){addedge(u,v),addedge(v,u);
}
void dfs(int u, int fa){siz[u]=1;for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;dfs(v,u);siz[u]+=siz[v],son[u]=max(son[u],siz[v]);}son[u]=max(son[u],n-siz[u]);if(son[u]<son[ans])ans=u;else if(son[u]==son[ans]&&u<ans)ans=u;
}
int main(){read(T);while(T--){read(n);memset(siz,0,sizeof siz);memset(son,0,sizeof son);memset(head,0,sizeof head);cnt=ans=0,son[0]=999999999;for(reg int i=1;i<n;i++)read(u),read(v),superadd(u,v);dfs(1,0);printf("%d %d\n",ans,son[ans]);}
}

2.树的直径

这里不得不 d i s s diss diss一下 s b sb sb一本通,直径两个字一笔没提,求法就给一个
定义:一棵树中,任意两个点的最短路径的距离最大值即为树的直径
有两种求法
1. 1. 1.两次 d f s : dfs: dfs:实质是遍历点
2. 2. 2.树形 d p : dp: dp:实质是遍历边

求法 1 : 1: 1:两次 d f s dfs dfs ( ( (或两次 b f s bfs bfs ) ) )
做法:先从任意一点 P P P开始,找到距离他最远的点 Q Q Q;再从 Q Q Q找到距离最远的点 W W W, Q W QW QW即为树的直径

证明:①若 P P P是直径上的一点,则 Q Q Q是直径的端点, Q W QW QW就是直径
②若 P P P不在直径上,反证法:
假设 Q W QW QW不是直径, A B AB AB是直径,则 Q Q Q一定不在直径上
( 1 ) (1) (1)当 A B AB AB与 P Q PQ PQ有交点时,如图(为了方便把 A B AB AB和 P Q PQ PQ画成直线)

我们选的 Q Q Q是距离 P P P最远的点,则 P Q > P O + O A PQ>PO+OA PQ>PO+OA
两边同时减去 P O PO PO得 O Q > O A OQ>OA OQ>OA
两边同时加上 O B OB OB得 O Q + O B > A B OQ+OB>AB OQ+OB>AB,与 A B AB AB是直径矛盾
( 2 ) (2) (2)当 A B AB AB与 P Q PQ PQ没有交点时,如图
P Q > N P + N M + N B PQ>NP+NM+NB PQ>NP+NM+NB,两边同时减去 N P NP NP,得 N Q > N M + M B NQ>NM+MB NQ>NM+MB
两边同时加上 A M + M N AM+MN AM+MN,得 A M + M N + N Q > A M + 2 N M + M B > A M + M B = A B AM+MN+NQ>AM+2NM+MB>AM+MB=AB AM+MN+NQ>AM+2NM+MB>AM+MB=AB
与 A B AB AB为直径矛盾
故 Q Q Q在直径上,且为直径的端点,所以 Q W QW QW为直径
来个板子题

FZOJ 3398 树的直径

题目描述
树的直径:树上两点之间的最大距离。
给出一个树,让你求树的直径。
输入
一个数n表示节点数,以下(n-1)行每行两个数x,y表示x与y间有边。
输出
一个整数,树的直径。
样例输入
10
2 8
7 2
2 1
1 10
2 3
3 4
4 9
3 5
3 6
样例输出
5
提示
40% n<=2000
100% n<=200000

上代码

#include<bits/stdc++.h>
using namespace std;
#define N 200020
#define reg register
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
int n,u,v,p,q,cnt,dep[N],head[N];
struct node{int to,nxt;
}edge[N<<1];
inline void addedge(int u, int v){edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline void superadd(int u, int v){addedge(u,v),addedge(v,u);
}
void dfs(int u, int fa){dep[u]=dep[fa]+1;for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;dfs(v,u);}if(dep[u]>dep[q])q=u;
}
int main(){read(n);for(reg int i=1;i<n;i++)read(u),read(v),superadd(u,v);dep[0]=-1;dfs(1,0);p=q,q=0;memset(dep,0,sizeof dep);dep[0]=-1;dfs(p,0);printf("%d\n",dep[q]);
}

求法 2 : 2: 2:树形 d p dp dp
这个方法也可以适用于有权值的树

一棵有根树的最长连,可能出现红色或黄色两种情况的直径
所以要解决这个问题,我们要求出每个结点为根的子树中的最长链,取其中的最大值为该树的直径
设 d p 1 [ u ] dp1[u] dp1[u]表示以 u u u为根的子树中, u u u到叶子结点的距离最大值; d p 2 [ u ] dp2[u] dp2[u]表示以 u u u为根的子树中, u u u到叶子节点的距离次大值
当遍历到 i i i的儿子 j j j时,
①若 d p 1 [ j ] + d i s [ i ] [ j ] > d p 1 [ i ] dp1[j]+dis[i][j]>dp1[i] dp1[j]+dis[i][j]>dp1[i],则 d p 2 [ i ] = d p 1 [ i ] , d p 1 [ i ] = d p 1 [ j ] + d i s [ i ] [ j ] dp2[i]=dp1[i],dp1[i]=dp1[j]+dis[i][j] dp2[i]=dp1[i],dp1[i]=dp1[j]+dis[i][j]
②若 d p 2 [ i ] < d p 1 [ j ] + d i s [ i ] [ j ] < d p 1 [ i ] dp2[i]<dp1[j]+dis[i][j]<dp1[i] dp2[i]<dp1[j]+dis[i][j]<dp1[i],则 d p 2 [ i ] = d p 1 [ j ] + d i s [ i ] [ j ] dp2[i]=dp1[j]+dis[i][j] dp2[i]=dp1[j]+dis[i][j]
最后扫描所有的结点,找最大的 d p 1 [ i ] + d p 2 [ i ] dp1[i]+dp2[i] dp1[i]+dp2[i]的值
上代码
就不找例题了 好累 哭哭

void dfs(int u, int fa){for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;dfs(v,u);if(dp1[u]<dp1[v]+edge[i].val)dp2[u]=dp1[u],dp1[u]=dp1[v]+edge[i].val;else if(dp2[u]<dp1[v]+edge[i].val)dp2[u]=dp1[v]+edge[i].val;ans=max(ans,dp1[u]+dp2[u]);}
}

三、一本通题目

行 知识点就这么多够了 来上题吧
书里的例题分为五个题型
1. 1. 1.由根分成左子树和右子树两部分的情况
2. 2. 2.背包类树形 D P DP DP
3. 3. 3.求树的最长链问题
4. 4. 4.覆盖一棵树上所有边
5. 5. 5.覆盖一棵树上的所有点
就一个一个按顺序来了

题型 1. 1. 1.二叉苹果树

题面

#include<bits/stdc++.h>
using namespace std;
#define N 110
#define reg register
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
int n,q,u,v,w,cnt,a[N],head[N],ls[N],rs[N],dp[N][N];
struct node{int to,nxt,val;
}edge[N<<1];
inline void addedge(int u, int v, int w){edge[++cnt].to=v,edge[cnt].val=w,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline void superadd(int u, int v, int w){addedge(u,v,w),addedge(v,u,w);
}
void build(int u, int fa){int g=0;for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;if(!g)ls[u]=v,a[v]=edge[i].val,g++;else rs[u]=v,a[v]=edge[i].val;build(v,u);}
}
int dfs(int u, int num){if(!num)return 0;if(!ls[u]&&!rs[u])return a[u];if(dp[u][num])return dp[u][num];for(reg int k=0;k<num;k++)dp[u][num]=max(dp[u][num],dfs(ls[u],k)+dfs(rs[u],num-k-1)+a[u]);return dp[u][num];
}
int main(){read(n),read(q);for(reg int i=1;i<n;i++)read(u),read(v),read(w),superadd(u,v,w);build(1,0);printf("%d\n",dfs(1,q+1));
}

题型 2. 2. 2.选课

题面
S o l u t i o n : Solution: Solution:树 d p + dp+ dp+背包
首先,肯定要从没有先修课的课开始选,所以要从跟 0 0 0相连的边开始选。
故直接将 0 0 0设为根就可以了,结点数变为 n + 1 n+1 n+1
设 d p [ u ] [ t ] dp[u][t] dp[u][t]表示在 x x x为根的子树中选 t t t门课能够获得的最高学分
这就转化成了一个背包问题
倒序枚举空间 ( ( (即选课总门数 ) ) ) 正序枚举物品 ( ( (即传递给子树的选课门数 ) ) )
状态转移方程: d p [ u ] [ t ] = m a x j = 0 t { d p [ u ] [ t − j ] + d p [ v ] [ j ] } dp[u][t]=max_{j=0}^{t}\{dp[u][t-j]+dp[v][j]\} dp[u][t]=maxj=0t​{dp[u][t−j]+dp[v][j]}
上代码↓

#include<bits/stdc++.h>
using namespace std;
#define N 110
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
int n,m,u,cnt,head[N],val[N],dp[N][N];
struct node{int to,nxt;
}edge[N<<1];
inline void addedge(int u, int v){edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline void superadd(int u, int v){addedge(u,v),addedge(v,u);
}
void dfs(int u, int fa){dp[u][1]=val[u];for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;dfs(v,u);for(int t=m;t;t--)for(int j=0;j<t;j++)dp[u][t]=max(dp[u][t],dp[u][t-j]+dp[v][j]);}
}int main(){read(n),read(m);for(int i=1;i<=n;i++)read(u),read(val[i]),superadd(u,i);m++;dfs(0,-1);printf("%d\n",dp[0][m]);
}

题型 3. 3. 3.数字转换

题面
S o l u t i o n : Solution: Solution:把可以转化的数和约数和连上边,这样就组成了一棵树。题中要求的最大变换即为树的直径,就用到了上面树的直径的例题。
这题还要判定一个数只能转化为比他小的约数和,所以我们要判断一个边是否能连上,间接地遍历了边集,所以用树形 d p dp dp求直径更为简洁
( ( (也可以用两次 d f s dfs dfs,但是上面写了两次 d f s dfs dfs的题,这道就用树形 d p dp dp叭 ) ) )
P S : PS: PS:如果懒得建图一定要注意遍历顺序是从大数到小数!
上代码↓

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define N 50005
int n,ans,a[N],dis1[N],dis2[N];
void dp(){for(reg int i=n;i;i--){if(a[i]<i){if(dis1[i]+1>dis1[a[i]])dis2[a[i]]=dis1[a[i]],dis1[a[i]]=dis1[i]+1;else if(dis1[i]+1>dis2[a[i]])dis2[a[i]]=dis1[i]+1;}}
}
int main(){cin>>n;for(int i=1;i<=n;i++){for(int j=2;j<=n/i;j++)a[i*j]+=i;}dp();for(reg int i=1;i<=n;i++)if(dis1[i]+dis2[i]>ans)ans=dis1[i]+dis2[i];cout<<ans<<endl;
}

题型 4. 4. 4.战略游戏

题面
不懂一本通上说的什么树的最大独立集,就把他改成覆盖树上所有点了…
S o l u t i o n : Solution: Solution:
设 d p [ u ] [ 0 ] dp[u][0] dp[u][0]表示 u u u点不放士兵时,以 u u u为根的子树最少需要的士兵数
d p [ u ] [ 1 ] dp[u][1] dp[u][1]表示 u u u点不放士兵时,以 u u u为根的子树最少需要的士兵数
那么当 u u u点不放士兵时,因为士兵要看到所有的路,所以所有 u u u的儿子结点都要放士兵
即 d p [ u ] [ 0 ] = ∑ v ∈ s o n [ u ] d p [ v ] [ 1 ] dp[u][0]=\sum\limits_{v∈son[u]}{dp[v][1]} dp[u][0]=v∈son[u]∑​dp[v][1]
当 u u u点放士兵时, u u u的儿子结点放不放士兵都可以,那么加上 d p [ u ] [ 1 ] dp[u][1] dp[u][1]和 d p [ u ] [ 0 ] dp[u][0] dp[u][0]中最小的就可以了
即 d p [ u ] [ 1 ] = ∑ v ∈ s o n [ u ] m i n { d p [ v ] [ 0 ] , d p [ v ] [ 1 ] } dp[u][1]=\sum\limits_{v∈son[u]}{min\{dp[v][0],dp[v][1]\}} dp[u][1]=v∈son[u]∑​min{dp[v][0],dp[v][1]}
上代码↓

#include<bits/stdc++.h>
using namespace std;
#define N 1515
#define reg register
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
int u,v,n,m,cnt,head[N],dp[N][2];
struct node{int nxt,to;
}edge[N<<1];
inline void addedge(int u, int v){edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline void superadd(int u, int v){addedge(u,v),addedge(v,u);
}
void dfs(int u, int fa){dp[u][1]=1;for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;dfs(v,u);dp[u][0]+=dp[v][1];dp[u][1]+=min(dp[v][0],dp[v][1]);}
}
int main(){read(n);for(reg int i=1;i<=n;i++){read(u),read(m);for(reg int j=1;j<=m;j++)read(v),superadd(u,v);}dfs(0,-1);printf("%d\n",min(dp[0][0],dp[0][1]));
}

题型 5. 5. 5.皇宫看守

题面
S o l u t i o n : Solution: Solution:
这道题和上道题的区别是,上道题是覆盖所有边,这道题是覆盖所有点。
覆盖所有边要用两个状态,而覆盖所有点需要用三个状态
① ① ①在父结点安排警卫
d p [ u ] [ 0 ] dp[u][0] dp[u][0]表示 u u u结点在父亲结点可以被看到时,以 u u u为根的子树最少需要安排的士兵数
② ② ②在当前结点安排警卫
d p [ u ] [ 1 ] dp[u][1] dp[u][1]表示 u u u结点安排警卫时,以 u u u为根的子树需要安排的最少士兵数
③ ③ ③在子结点安排警卫
d p [ u ] [ 2 ] dp[u][2] dp[u][2]表示 u u u结点能被它的至少一个子节点看到时,以 u u u为根的子树最少需要安排的士兵数

对于 d p [ u ] [ 0 ] , dp[u][0], dp[u][0],表示 u u u结点能被父亲结点看到,这时 u u u结点不用安排警卫,子结点要么安排警卫,要么被它的子结点看到,所以有
d p [ u ] [ 0 ] = ∑ v ∈ s o n [ u ] m i n { d p [ v ] [ 1 ] , d p [ v ] [ 2 ] } dp[u][0]=\sum\limits_{v∈son[u]}{min\{dp[v][1],dp[v][2]\}} dp[u][0]=v∈son[u]∑​min{dp[v][1],dp[v][2]}
( P S . (PS. (PS.一本通又双叒叕写错了… ) ) )

对于 d p [ u ] [ 1 ] , dp[u][1], dp[u][1],表示 u u u结点安排了警卫, u u u的儿子 v v v可以安排警卫,也可以被子结点看守,还可以被它的父结点 u u u看守,所以有
d p [ u ] [ 1 ] = c o s t [ u ] + ∑ v ∈ s o n [ u ] m i n { d p [ v ] [ 0 ] , d p [ v ] [ 1 ] , d p [ v ] [ 2 ] } dp[u][1]=cost[u]+\sum\limits_{v∈son[u]}{min\{dp[v][0],dp[v][1],dp[v][2]\}} dp[u][1]=cost[u]+v∈son[u]∑​min{dp[v][0],dp[v][1],dp[v][2]}
P S . PS. PS.不要忘记加上自身的花费 c o s t [ u ] cost[u] cost[u]

对于 d p [ u ] [ 1 ] , dp[u][1], dp[u][1],表示 u u u能被至少一个子结点看到,此时 u u u结点没有被安排警卫,子结点需要安排警卫或被它的后代看到,所以我们要比较这两个方案哪个更优
但问题来了,如果所有的子结点都是被后代看到更优,那么就没有结点看到 u u u结点了,这就是为什么我加粗了至少一个,这时候我们就要判断用哪个子结点安排警卫最优
所以当所有的 d p [ v ] [ 1 ] > d p [ v ] [ 2 ] dp[v][1]>dp[v][2] dp[v][1]>dp[v][2]时,维护一个 r = m i n v ∈ s o n [ u ] { d p [ v ] [ 1 ] − d p [ v ] [ 2 ] } r=min_{v∈son[u]}\{dp[v][1]-dp[v][2]\} r=minv∈son[u]​{dp[v][1]−dp[v][2]},最后总结果加上 r r r的意义是使一个子结点安排警卫来看守 u u u结点
所以有
d p [ u ] [ 2 ] = r + ∑ v ∈ s o n [ u ] m i n { d p [ v ] [ 1 ] , d p [ v ] [ 2 ] } dp[u][2]=r+\sum\limits_{v∈son[u]}{min\{dp[v][1],dp[v][2]\}} dp[u][2]=r+v∈son[u]∑​min{dp[v][1],dp[v][2]}
其中 r = m i n { d p [ v ] [ 1 ] − m i n { d p [ v ] [ 1 ] , d p [ v ] [ 2 ] } } r=min\{dp[v][1]-min\{dp[v][1],dp[v][2]\}\} r=min{dp[v][1]−min{dp[v][1],dp[v][2]}}
P S . PS. PS.这样写 r r r的意思是,如果子结点有安排警卫的,那么 r = 0 r=0 r=0表示不用补子结点
如果都没有 那么 r = d p [ v ] [ 1 ] − d p [ v ] [ 2 ] r=dp[v][1]-dp[v][2] r=dp[v][1]−dp[v][2]表示让 v v v结点从被子结点看到改为自己安排警卫
我这块理解了很长时间 所以就多码点字 哈哈
上代码↓

#include<bits/stdc++.h>
using namespace std;
#define N 1515
#define reg register
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
int n,m,u,v,cnt,head[N],dp[N][3],c[N];
struct node{int to,nxt;
}edge[N<<1];
inline void addedge(int u, int v){edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline void superadd(int u, int v){addedge(u,v),addedge(v,u);
}
void dfs(int u, int fa){int r=2e9;dp[u][1]=c[u];for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;dfs(v,u);dp[u][0]+=min(dp[v][1],dp[v][2]);dp[u][1]+=min(dp[v][0],min(dp[v][1],dp[v][2]));dp[u][2]+=min(dp[v][1],dp[v][2]),r=min(r,dp[v][1]-min(dp[v][1],dp[v][2]));}dp[u][2]+=r;
}
int main(){read(n);for(reg int i=1;i<=n;i++){read(u),read(c[u]),read(m);for(reg int j=1;j<=m;j++)read(v),superadd(u,v);}dfs(1,0);printf("%d\n",min(dp[1][1],dp[1][2]));
}

后面就是练习题了

加分二叉树

题面
S o l u t i o n : Solution: Solution:数据范围这么小忍不住用区间 d p dp dp

#include<bits/stdc++.h>
using namespace std;
#define N 50
typedef long long ll;
int n,root[N][N];
ll dp[N][N],ans;
inline void read(ll &x){ll s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
void print(int l, int r){if(l>r)return ;printf("%d ",root[l][r]);if(l==r)return ;print(l,root[l][r]-1),print(root[l][r]+1,r);
}
int main(){cin>>n;for(int i=1;i<=n;i++)read(dp[i][i]),root[i][i]=i;for(int l=1;l<n;l++){for(int i=1,j=l+1;j<=n;i++,j++){dp[i][j]=dp[i+1][j]+dp[i][i];root[i][j]=i;for(int k=i+1;k<j;k++){ll now=dp[i][k-1]*dp[k+1][j]+dp[k][k];if(dp[i][j]<now)dp[i][j]=now,root[i][j]=k;}    }}printf("%lld\n",dp[1][n]);print(1,n);
}

旅游规划

题面
S o l u t i o n : Solution: Solution:这道题可能有多条直径,而且要求出所有在直径上的点,所以用树形 d p dp dp求树的直径
还是往常的直径的长度 l e n = m a x { d i s 1 [ i ] len=max\{dis1[i] len=max{dis1[i]和 d i s [ 2 ] } dis[2]\} dis[2]}
但是问题来了,如果一个点 u u u在直径上,但他只有两条边,一条连向父亲 f a fa fa,一条连向儿子 v v v,那么他的 d i s 2 dis2 dis2值会为 0 0 0,但实际上他也在直径上
这个问题的出现的原因是因为我们把 d f s dfs dfs设成单向,不让一个点去重复遍历他的父亲结点,因为他遍历不了父亲结点,所以才导致 d i s 2 dis2 dis2值为 0 0 0,故我们需要求出此节点在父亲结点方向链长的最大值
如何解决这个问题呢
我们设置两个数组 s o n 1 [ i ] = v son1[i]=v son1[i]=v表示 i i i点的 d i s 1 [ i ] dis1[i] dis1[i]值是从 v v v点转移的, m o v e [ i ] move[i] move[i]表示 i i i点在父亲结点方向的链长最大值
分两种情况
1. 1. 1.如果 s o n [ f a ] = u son[fa]=u son[fa]=u:说明 u u u就在 f a fa fa求得 d i s 1 dis1 dis1值的链中,转移 m o v e [ u ] move[u] move[u]不能从 d i s 1 dis1 dis1中转移。那么又有两种情况, f a fa fa有 d i s 2 dis2 dis2和没有 d i s 2 dis2 dis2,所以 m o v e [ u ] = m a x { d i s 2 [ f a ] , m o v e [ f a ] } move[u]=max\{dis2[fa],move[fa]\} move[u]=max{dis2[fa],move[fa]}
2. 2. 2.如果 s o n [ f a ] ≠ u son[fa]\neq u son[fa]​=u:这时候转移可以从 d i s 1 dis1 dis1转移了,所以 m o v e [ u ] = m a x { d i s 1 [ f a ] , m o a v [ f a ] } + 1 move[u]=max\{dis1[fa],moav[fa]\}+1 move[u]=max{dis1[fa],moav[fa]}+1
最后判断 m [ i ] + d i s 1 [ i ] = l e n m[i]+dis1[i]=len m[i]+dis1[i]=len或 d i s 1 [ i ] + d i s 2 [ i ] = l e n dis1[i]+dis2[i]=len dis1[i]+dis2[i]=len时说明 i i i在直径上
上代码

#include<bits/stdc++.h>
using namespace std;
#define N 200020
#define reg register
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
int n,u,v,ans,cnt,head[N],dis1[N],dis2[N],son1[N],m[N];
struct node{int to,nxt;
}edge[N<<1];
inline void addedge(int u, int v){edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline void superadd(int u, int v){addedge(u,v),addedge(v,u);
}
void dfs(int u, int fa){for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;dfs(v,u);if(dis1[u]<dis1[v]+1)dis2[u]=dis1[u],dis1[u]=dis1[v]+1,son1[u]=v;else if(dis2[u]<dis1[v]+1)dis2[u]=dis1[v]+1;}ans=max(ans,dis1[u]+dis2[u]);
}
void df5(int u, int fa){if(u!=1){if(son1[fa]==u)m[u]=max(dis2[fa],m[fa])+1;else m[u]=max(dis1[fa],m[fa])+1;}for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v!=fa)df5(v,u);}
}
int main(){read(n);for(reg int i=1;i<n;i++)read(u),read(v),superadd(++u,++v);dfs(1,0);df5(1,0);for(reg int i=1;i<=n;i++)if(m[i]+dis1[i]==ans||dis1[i]+dis2[i]==ans)printf("%d\n",i-1);
}

周年纪念晚会

题面
S o l u t i o n : Solution: Solution:和没有上司的舞会一样
d p [ u ] [ 0 ] dp[u][0] dp[u][0]表示 u u u不去, d p [ u ] [ 1 ] dp[u][1] dp[u][1]表示 u u u去
那么
d p [ u ] [ 0 ] = ∑ v ∈ s o n [ u ] m i n { d p [ v ] [ 0 ] , d p [ v ] [ 1 ] } dp[u][0]=\sum\limits_{v∈son[u]}min\{dp[v][0],dp[v][1]\} dp[u][0]=v∈son[u]∑​min{dp[v][0],dp[v][1]}
d p [ u ] [ 1 ] = ∑ v ∈ s o n [ u ] d p [ v ] [ 0 ] dp[u][1]=\sum\limits_{v∈son[u]}dp[v][0] dp[u][1]=v∈son[u]∑​dp[v][0]
代码↓

#include<bits/stdc++.h>
using namespace std;
#define N 6060
#define reg register
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
int n,u,v,cnt,head[N],dp[N][2],a[N];
struct node{int to,nxt;
}edge[N<<1];
inline void addedge(int u, int v){edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline void superadd(int u, int v){addedge(u,v),addedge(v,u);
}
void dfs(int u, int fa){dp[u][1]=a[u];for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;dfs(v,u);dp[u][0]+=max(dp[v][0]+dp[v][1]);dp[u][1]+=dp[v][0];}
}
int main(){read(n);for(reg int i=1;i<=n;i++)read(a[i]);while(1){read(u),read(v);if(u==0&&v==0)break;superadd(u,v);}dfs(1,0);printf("%d\n",max(dp[1][0],dp[1][1]));
}

叶子的颜色 ( C Q O I 2009 (CQOI2009 (CQOI2009叶子的染色 ) ) )

题面
S o l u t i o n : Solution: Solution:首先一个结点有三种状态: 1. 1. 1.黑色 2. 2. 2.白色 3. 3. 3.无色
我们可以发现,叶子结点的染色方案只与上一个有色结点有关
这句话有两个意思
1. 1. 1.无色结点与答案无关,因为它做不出任何贡献
2. 2. 2.根结点与答案无关,因为答案只与叶子结点的上一个有色结点有关
设 d p [ u ] [ 0 ] dp[u][0] dp[u][0]为 u u u结点染成黑色时,以 u u u为根的子树需要的最少代价
d p [ u ] [ 1 ] dp[u][1] dp[u][1]为 u u u结点染成白色时,以 u u u为根的子树需要的最少代价
那么 d p [ u ] [ 0 ] = 1 + ∑ v ∈ s o n [ u ] m i n { d p [ v ] [ 0 ] − 1 , d p [ v ] [ 1 ] } dp[u][0]=1+\sum\limits_{v∈son[u]}min\{dp[v][0]-1,dp[v][1]\} dp[u][0]=1+v∈son[u]∑​min{dp[v][0]−1,dp[v][1]}
d p [ u ] [ 1 ] = 1 + ∑ v ∈ s o n m i n { d p [ v ] [ 0 ] , d p [ v ] [ 1 ] − 1 } dp[u][1]=1+\sum\limits_{v∈son}min\{dp[v][0],dp[v][1]-1\} dp[u][1]=1+v∈son∑​min{dp[v][0],dp[v][1]−1}
初始条件
d p [ u ] [ c [ u ] ] = 1 , d p [ u ] [ ! c [ u ] ] = i n f u ∈ [ 1 , n ] dp[u][c[u]]=1,dp[u][!c[u]]=inf\,\,\,\,\,\,\,\,\,\,\,\,\,u∈[1,n] dp[u][c[u]]=1,dp[u][!c[u]]=infu∈[1,n]
d p [ u ] [ 0 ] = 1 , d p [ u ] [ 1 ] = 1 u ∈ [ n + 1 , m ] dp[u][0]=1,dp[u][1]=1\,\,\,\,\,\,\,\,\,\,\,\,\,u∈[n+1,m] dp[u][0]=1,dp[u][1]=1u∈[n+1,m]
上代码↓

#include<bits/stdc++.h>
using namespace std;
#define N 10010
#define reg register
#define inf 2000000000
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
int n,m,u,v,cnt,head[N],dp[N][2],c[N];
struct node{int to,nxt;
}edge[N<<1];
inline void addedge(int u, int v){edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
inline void superadd(int u, int v){addedge(u,v),addedge(v,u);
}
void dfs(int u, int fa){for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==fa)continue;dfs(v,u);dp[u][0]+=min(dp[v][0]-1,dp[v][1]);dp[u][1]+=min(dp[v][0],dp[v][1]-1);}
}
int main(){read(m),read(n);for(reg int i=1;i<=n;i++)read(c[i]),dp[i][c[i]]=1,dp[i][!c[i]]=inf;for(reg int i=1;i<m;i++)read(u),read(v),superadd(u,v);for(reg int i=1+n;i<=m;i++)dp[i][0]=dp[i][1]=1;dfs(n+1,0);printf("%d\n",min(dp[n+1][0],dp[n+1][1]));
}

骑士

题面
S o l u t i o n : Solution: Solution:模型是没有上司的舞会的模型,只不过把每个骑士和他烦的骑士连上边,是颗有 n n n个结点 n n n条边的基环树
那么我们找到环,把环中的一条边断开,那么就变成了一棵树,再对断开的两个结点分别做根进行树 d p dp dp。因为断开的两个结点也是有条件约束的,就强制在一个结点遍历时不遍历另一个结点。
树中会有很多联通块,对每个联通块都进行一次找环的操作,所有答案加起来就是最后的答案
实现时要注意:
1. 1. 1.边要连单向边,并存一个 f f f数组指向边的反方向,来存储上一个结点的信息
2. 2. 2.环只有一个,联通块不是一个,要对每个联通块都求一遍环
3. a n s 3.ans 3.ans要开 l o n g l o n g long\,\,\,\,long longlong
上代码↓

#include<bits/stdc++.h>
using namespace std;
#define reg register
#define N 1000100
typedef long long ll;
inline void read(int &x){int s=0,w=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch&15);ch=getchar();}x=s*w;
}
ll ans,dp[N][2];
bool vis[N];
int n,u,v,rt,cnt,head[N],c[N],f[N];
struct node{int to,nxt;
}edge[N];
inline void addedge(int u, int v){edge[++cnt].to=v,edge[cnt].nxt=head[u],head[u]=cnt;
}
void dfs(int u){vis[u]=1;dp[u][0]=0,dp[u][1]=c[u];for(reg int i=head[u];i;i=edge[i].nxt){int v=edge[i].to;if(v==rt)continue;dfs(v);dp[u][0]+=max(dp[v][1],dp[v][0]);dp[u][1]+=dp[v][0];}
}
inline void judge(int x){rt=x,vis[x]=1;while(!vis[f[rt]])rt=f[rt],vis[rt]=true;dfs(rt);ll p=dp[rt][0];vis[rt]=true,rt=f[rt];dfs(rt);ll q=dp[rt][0];ans+=max(p,q);
}
int main(){read(n);for(reg int i=1;i<=n;i++)read(c[i]),read(v),addedge(v,i),f[i]=v;for(reg int i=1;i<=n;i++)if(!vis[i])judge(i);printf("%lld\n",ans);
}

四、总结

讲个故事
C S P 2019 CSP2019 CSP2019前在家跟同学语音刷题,突然发现好多蓝树 d p dp dp题都变成了绿色,我就跟同学提了嘴是不是出题组为了出树 d p dp dp才把标签弄简单的,结果没想到三个树 d p dp dp

总的来说,树形 d p dp dp是一个能解决许多树上问题的一个优秀高效算法,在联赛中也屡见不鲜。
本人 Q Q : 407694747 QQ:407694747 QQ:407694747,欢迎各位大佬一起来讨论

一本通提高篇 树形动态规划相关推荐

  1. 《信息学奥赛一本通 提高篇》

    提高篇 第一部分 基础算法 第1章 贪心算法 提高篇 第一部分 基础算法 第1章 贪心算法_青少年趣味编程-CSDN博客 提高篇 第一部分 基础算法 第1章 贪心算法 提高篇 第一部分 基础算法 第1 ...

  2. 一本通提高篇在线提交地址

    一本通提高篇 1 基础算法 1.1 贪心算法 1.1.1 P2018  [第一章例题1.1]活动安排正确: 9 提交: 17 比率: 52.94 % 1.1.2 P2021 [第一章例题1.2]种树正 ...

  3. 信息学奥赛一本通 提高篇 第一部分 基础算法 第2章 二分与三分

    信息学奥赛一本通 提高篇 提高版 第一部分 基础算法 第2章 二分与三分 信息学奥赛一本通 提高篇 提高版 第一部分 基础算法 第2章 二分与三分_mrcrack的博客-CSDN博客_信息学奥赛一本通 ...

  4. 信息学奥赛一本通 提高篇 第六部分 数学基础 第1章 快速幂

    信息学奥赛一本通 提高篇 第六部分 数学基础 第1章 快速幂 https://blog.csdn.net/mrcrack/article/details/82846727 快速幂取模算法如何实现? h ...

  5. 一本通提高篇 AC自动机

    本来想写树状数组的 好像mymymy申对这棵树有点迷茫 不过他不回我 那就接着来AVC自动机吧 UPD:20200517UPD:20200517UPD:20200517期中考试考完了 确实考完了-这辈 ...

  6. 一本通-提高篇-图论-割点和桥:

    一本通: 提高篇: 图论: 割点和桥: 1520:[ 例 1]分离的路径 题意:如何把有桥图通过加边变成边双连通分量 如果叶子数(缩点后度为1的点)为1,则至少需要添加0条边: 否则为(叶子数+1)/ ...

  7. 信息学奥赛一本通 提高篇 第6章 平衡树Treap

    随笔分类 - 动态规划--树形动态规划 动态规划--树形动态规划 - 随笔分类 - 符拉迪沃斯托克 - 博客园 平衡树 Treap 平衡树_百度百科 平衡树--treap - genius777 - ...

  8. 一本通提高篇 哈希和哈希表(一)哈希

    写在前面 C S P 2019 d a y 1 CSP2019day1 CSP2019day1 无脑挂掉 150 ! 150! 150! 再次被老师嘲讽没考过 0 0 0基础的 这么一说 学 O I ...

  9. 《信息学奥赛一本通提高篇》第6章 组合数学

    例1 计算系数(NOIP2011提高) 信息学奥赛一本通(C++版)在线评测系统 NOIP2011计算系数_nanhan27的博客-CSDN博客 「NOIP2011」 计算系数 - 组合数_TbYan ...

最新文章

  1. python accept解析_python中requests库使用方法详解
  2. JavaScript的undefined
  3. python画圆填色_python turtle我想用五种颜色画五个圆,并且用画圆周的颜色填充,老是出问题,怎么回事,怎么修改?:python教程同心圆...
  4. 【牛客 - 317F】小a的子序列(线性dp,tricks)
  5. 《Adobe After Effects CS4经典教程》——1.9 定制工作区
  6. CentOS7 python django框架 天天生鲜项目 搭建流程
  7. zte中兴应用Java版下载_中兴link app下载
  8. 【转贴】会看会做会转换——PDF文件应用宝典-教程-pdf中国
  9. 公司要一个网站,是选模板建站还是定制化建站?
  10. 浅谈欧奈尔对利弗莫尔的继承和发扬
  11. 火灾自动报警系统学习心得
  12. 卡尔沃宁方法 | 计算运动目标心率
  13. mybatis学习1
  14. Android开发之小程序-秒表
  15. 使用动态规划算法需要满足的必要条件:优化原则
  16. swagger(三):统一返回结果不显示字段说明
  17. python moviepy 从视频中提取音频
  18. 栈的基础与基本操作实现
  19. Vuze(AKA Azureus) 4.5 发布
  20. 爬虫实战-python爬取QQ群好友信息

热门文章

  1. python图像识别处理
  2. 基于windows和linux下的各种操作命令
  3. 光栅化 Rasterization
  4. 你身边有个这样的「小黄」么?
  5. 力扣739:每日温度 medium 20220329
  6. 0基础C语言实战项目-贪吃蛇小游戏
  7. Java 重写paint绘图
  8. CentOS7安装配置SpaceVim
  9. SpringBoot项目中过滤器Filter的配置
  10. 如何开通股票接口中的StockQuoteRecord功能?