树形DP
树形DP没有固定的模板,见多了题,就知道该如何分析,如何设计状态了。所以该篇博客以例题来进行讲解。
树的直径
本题是一道树的直径板子题。
求树的直径有两种方法:
(1)两遍 dfs。(2)树形DP。
时间复杂度都是
这里我们用树形DP来解决该题。
思路
首先,选择任意一点为根。
如果一个点是直径上的一点,那树的直径等于从该点出发的最长链+从该点出发的次长链。
设计状态
设
状态转移
树形DP一般就是递归由子孙节点的信息去更新祖先节点的信息。
设
因为最长链和次长链不可以重合,所以我们用最长链去更新次长链。
①若
②否则,若
用代码解释就是:
点击查看代码
if(dp[x][0]<dp[v][0]+1){
dp[x][1]=dp[x][0];
dp[x][0]=dp[v][0]+1;
}else if(dp[x][1]<dp[v][0]+1){
dp[x][1]=dp[v][0]+1;
}
初始化
都为0。
代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
inline int read(){
int w=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-1') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w*10+ch-'0';
ch=getchar();
}
return w*f;
}
int n;
int dp[10005][2],ans;
vector<int> g[10005];
void dfs(int x,int fa){
for(int i=0;i<g[x].size();i++){
int v=g[x][i];
if(v==fa) continue;
dfs(v,x);
if(dp[x][0]<dp[v][0]+1){
dp[x][1]=dp[x][0];
dp[x][0]=dp[v][0]+1;
}else if(dp[x][1]<dp[v][0]+1){
dp[x][1]=dp[v][0]+1;
}
}
ans=max(ans,dp[x][0]+dp[x][1]);
}
int main(){
n=read();
for(int i=1;i<n;i++){
int u=read(),v=read();
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
cout<<ans;
return 0;
}
树的中心
这个蒟蒻没有权限看这道题
题意:一棵树,边有权,找出一个点,使得这个点距离其他点的最大距离最小。
思路
首先,选择任意一点为根。
一个点的最远点的位置有两种情况。
(1)以该点为根的子树内;
(2)以该点为根的子树外。
子树内的好求,和上面那题一模一样。
字数外呢?新设一个变量。
设计状态
设
设
设
状态转移
首先一遍树形DP求出
同上题。
然后,设
①若
②否则,若
初始化
都为0。
代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
inline int read(){
int w=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w*10+ch-'0';
ch=getchar();
}
return w*f;
}
int n;
int dp[10005][2],c[10005][2],out[10005],ans=0x7fffffff;
vector<pair<int,int> > g[10005];
void dfs1(int x,int fa){
for(int i=0;i<g[x].size();i++){
int v=g[x][i].second;
int w=g[x][i].first;
if(v==fa) continue;
dfs1(v,x);
if(dp[x][0]<dp[v][0]+w){
c[x][1]=c[x][0];
dp[x][1]=dp[x][0];
c[x][0]=v;
dp[x][0]=dp[v][0]+w;
}else if(dp[x][1]<dp[v][0]+w){
c[x][1]=v;
dp[x][1]=dp[v][0]+w;
}
}
}
void dfs2(int x,int fa){
for(int i=0;i<g[x].size();i++){
int v=g[x][i].second;
int w=g[x][i].first;
if(v==fa) continue;
if(c[x][0]!=v) out[v]=max(out[x],dp[x][0])+w;
else if(c[x][1]!=v) out[v]=max(out[x],dp[x][1])+w;
dfs2(v,x);
}
}
int main(){
n=read();
for(int i=1;i<n;i++){
int u=read(),v=read(),w=read();
g[u].push_back(make_pair(w,v));
g[v].push_back(make_pair(w,u));
}
dfs1(1,0);
dfs2(1,0);
for(int i=1;i<=n;i++) ans=min(ans,max(out[i],dp[i][0]));
cout<<ans;
return 0;
}
没有上司的舞会
思路
先找到根,简化一下题意就是父亲和儿子不能同时被选,每个点只有两种状态,选或不选,且仅影响自己的父亲与儿子。树形DP。其实这里解释的很草率,但这就是个树形DP。可能做多了就有这种感觉了吧
设计状态
设
状态转移
设
最后答案就是
初始化
代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
inline int read(){
int w=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w*10+ch-'0';
ch=getchar();
}
return w*f;
}
int n,ha[6005];
bool ind[6005];
vector<int> g[6005];
int f[6005][2];
int roott;
void inp(){
n=read();
for(int i=1;i<=n;i++){
ha[i]=read();
}
//每个人的快乐值
for(int i=1;i<n;i++){
int upp,loww;
loww=read(),upp=read();
g[upp].push_back(loww);
ind[loww]=1;
}
//建树,ind为入度,用来找根(最大的上司)
}
void findroott(){
for(int i=1;i<=n;i++){
if(!ind[i]){
roott=i;
break;
}
}
}
//找根
void recursion(int x){
f[x][0]=0;
f[x][1]=ha[x];
int k=g[x].size();
for(int i=0;i<k;i++){
int v=g[x][i];
recursion(v);
f[x][0]+=max(f[v][0],f[v][1]);//自己不去,和自己相连的人可以去也可以不去,取最大值
f[x][1]+=f[v][0];//自己去,自己的儿子就不能去
}
}
//递归求值
int main(){
inp();
findroott();
recursion(roott);
printf("%d",max(f[roott][0],f[roott][1]));
//输出根去的价值大,还是不去的价值大
return 0;
}
保安站岗
思路
以点被谁覆盖进行分类:(1)被自己覆盖;(2)被父亲覆盖;(3)被儿子覆盖。
设计状态
表示以 为根的整棵子树被覆盖, 被自己覆盖的最小代价 表示以 为根的整棵子树被覆盖, 被父亲覆盖的最小代价 表示以 为根的整棵子树被覆盖, 被儿子覆盖的最小代价
状态转移方程
这个比较好想,自己控制了自己,儿子可以被爹,自己,儿子控制,三种情况取最小。最后再加上控制自己的代价。
2.
自己被父亲控制,所以儿子要么被自己控制,要么被儿子控制。这里不需要加上自己被父亲控制的代价,我们看看哪里用到了
3.
这里就需要注意了,自己被儿子控制(只要有一个儿子控制自己就行),儿子可能自己控制自己,也可能被自己的儿子控制。两者取最小。这里又分出两种情况
- 如果有任意一个儿子,是自己控制自己的,也就满足了该点可以被儿子控制。不用加minn
- 如果所有的儿子都是被自己的儿子控制,就无法满足该点被儿子控制,所以至少要找出一个儿子来自己控制自己,为了使答案更小,所以就找
最小的那个儿子,加上这个值,就满足了该点被其中一个儿子控制了。
最后答案:
根节点没有父亲,所以答案就取 。
代码
点击查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
inline int read(){
int w=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
w=w*10+ch-'0';
ch=getchar();
}
return w*f;
}
int n,w[1505],dp[1505][3];
vector<int> g[1505];
void dfs(int x,int fa){
dp[x][0]=w[x];
bool mark=0;
int minn=0x7fffffff;
for(int i=0;i<g[x].size();i++){
int v=g[x][i];
if(v==fa) continue;
dfs(v,x);
dp[x][0]+=min(dp[v][1],min(dp[v][2],dp[v][0]));
dp[x][1]+=min(dp[v][0],dp[v][2]);
if(dp[v][2]<dp[v][0]){
dp[x][2]+=dp[v][2];
minn=min(dp[v][0]-dp[v][2],minn);
}else{
dp[x][2]+=dp[v][0];
mark=1;
}
}
if(mark==0) dp[x][2]+=minn;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
int u=read();
w[u]=read();
int x=read();
while(x>0){
x--;
int v=read();
g[u].push_back(v);
g[v].push_back(u);
}
}
dfs(1,0);
printf("%d",min(dp[1][0],dp[1][2]));
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】