『学习笔记』树上dp
树上 dp,顾名思义,就是在树上进行动态规划。
既然 dp 是思想,那么就直接拿题来吧。
P1352 没有上司的舞会
题目大意
某大学有 n 个职员,编号为 1…n。
他们间的从属关系可以构成一棵以校长为根的有根树。
现在需要选择一些人来参加一个宴会,第 i 个职员来了会为宴会增加 ri 的快乐指数。但是,如果某个职员的直接上司来参加了宴会,这个人就一定不会来了。
这里的直接上司定义为某个职员的直接父亲节点。
求能够获得的最大快乐指数。
思路
显然,对于一个节点 u,我们是可以通过求出他的子节点 v 的答案来求出他的答案的。于是考虑 dp。
每个职员有两种情况:来与不来。
所以我们定义状态:fi,j(j∈{0,1}) 表示第 i 个职员,来与不来的情况下,他与他的所有子节点能够达到的最大快乐指数。
转移方程很好想:
-
u 不来:显然,u 的直接下属可以自由选择来与不来,那么 fu,0 就等于 ∑fv,0+fv,1 了。其中 v 分别表示 u 的一个子节点。若没有某个子节点,则相应的子节点值为 0。
-
u 来:那么下属就只能不来了。但是 u 来会获得快乐指数 ru,所以 fu,1=ru+∑fv,0。
那么转移方程就出来了:
对于初始条件,将 fi,j 全部设为 0 即可。
时间复杂度 O(n)。
答案即为 min{froot,0,froot,1}。
代码
#include <iostream>
using namespace std;
template<typename T=int>
inline T read(){
T X=0; bool flag=1; char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-') flag=0; ch=getchar();}
while(ch>='0' && ch<='9') X=(X<<1)+(X<<3)+ch-'0',ch=getchar();
if(flag) return X;
return ~(X-1);
}
const int N=6e3+5;
struct edge{
int to,nxt;
}e[N];
int n,u,v,rt;
int r[N],s[N]; // r 用于记录每个职员来的快乐指数,s 用于记录入度(判根节点)
int head[N],top;
int f[N][2]; // f[i][j]:第 i 个职员来(j=1)与不来(j=0)所能获得的最大快乐指数
void add(int u,int v){
top++;
e[top].to=v;
e[top].nxt=head[u];
head[u]=top;
}
void dp(int u){
// 初始化
f[u][0]=0,f[u][1]=r[u];
for(int i=head[u]; i; i=e[i].nxt){ // 遍历两个子节点
int v=e[i].to;
dp(v);
// 需要求出子树最优解后才能求当前节点的
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
}
}
int main(){
n=read();
for(int i=1; i<=n; i++)
r[i]=read();
for(int i=1; i<n; i++){
u=read(),v=read();
add(v,u);
s[u]++;
}
// 找根节点
for(int i=1; i<=n; i++)
if(!s[i]){
rt=i;
break;
}
dp(rt);
printf("%d\n",max(f[rt][0],f[rt][1]));
return 0;
}
P2016 战略游戏
题目大意
有一个城堡,其中的道路形成一棵无根树。当一个士兵站在其中某个节点上时,可以看守与这个节点有连边的节点。
现在给你这颗树,请你算出满足所有节点有士兵或被所有相邻节点的士兵看守,最少需要放多少士兵。
也就是说,一个节点要么是一个士兵,要么是一片空地,如果是空地,所有与这个空地相邻的节点都必须有士兵。
思路
显然,这题也可以通过某个节点子节点的解来求这个节点的解,可以 dp。
无脑设计状态:fi,j(j∈{0,1}) 表示第 i 个节点与它的所有后代满足要求,第 i 个节点放与不放士兵的最优解。
那么就来分别考虑两种情况(以 u 为父节点,v 为各个子节点):
-
不放士兵(j=0):不放的话,子节点就一定要放。故 fu,0=∑fv,1。
-
放士兵(j=1):这种情况子节点就自由选择,但不确定子节点放还是不放的解更优,所以 fu,1=∑max{fv,0,fv,1}+1,这里 +1 是因为 u 自己也要放一个士兵。
时间复杂度 O(n)。
代码
#include <iostream>
using namespace std;
template<typename T=int>
inline T read(){
T X=0; bool flag=1; char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-') flag=0; ch=getchar();}
while(ch>='0' && ch<='9') X=(X<<1)+(X<<3)+ch-'0',ch=getchar();
if(flag) return X;
return ~(X-1);
}
const int N=1e4+5;
struct edge{
int to,nxt;
}e[N];
int n,k,u,v;
int head[N],top;
int f[N][2];
void add(int u,int v){
top++;
e[top].to=v;
e[top].nxt=head[u];
head[u]=top;
}
void dp(int u,int fa){
f[u][0]=0,f[u][1]=1; // 初始化
for(int i=head[u]; i; i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue; // 防止死递归
dp(v,u);
f[u][0]+=f[v][1];
f[u][1]+=min(f[v][0],f[v][1]);
}
}
int main(){
n=read();
for(int i=1; i<=n; i++){
u=read()+1,k=read();
// 把下标从 [0,n) 变成 [1,n]
while(k--){
v=read()+1; // 同上
add(u,v);
add(v,u);
}
}
// 根节点的父亲随便写(恶臭/bx/bx
dp(1,114514);
printf("%d\n",min(f[1][0],f[1][1]));
return 0;
}
【推荐】2025 HarmonyOS 鸿蒙创新赛正式启动,百万大奖等你挑战
【推荐】博客园的心动:当一群程序员决定开源共建一个真诚相亲平台
【推荐】开源 Linux 服务器运维管理面板 1Panel V2 版本正式发布
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 我在厂里搞 wine 的日子
· 如何通过向量化技术比较两段文本是否相似?
· 35+程序员的转型之路:经济寒冬中的希望与策略
· JavaScript中如何遍历对象?
· 领域模型应用
· 独立项目运营一周年经验分享
· 独立开发,这条路可行吗?
· 文生图:介绍一个文字生成图片的开源工具
· Java简历、面试、试用期、转正
· MySQL 10 MySQL为什么有时候会选错索引?