Processing math: 100%

『学习笔记』树上dp

树上 dp,顾名思义,就是在树上进行动态规划。

既然 dp 是思想,那么就直接拿题来吧。

P1352 没有上司的舞会

题目大意

某大学有 n 个职员,编号为 1n

他们间的从属关系可以构成一棵以校长为根的有根树。

现在需要选择一些人来参加一个宴会,第 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={fv,0+fv,1j=0ru+fv,0j=1

对于初始条件,将 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;
}

习题

posted @   仙山有茗  阅读(87)  评论(0)    收藏  举报
编辑推荐:
· 我在厂里搞 wine 的日子
· 如何通过向量化技术比较两段文本是否相似?
· 35+程序员的转型之路:经济寒冬中的希望与策略
· JavaScript中如何遍历对象?
· 领域模型应用
阅读排行:
· 独立项目运营一周年经验分享
· 独立开发,这条路可行吗?
· 文生图:介绍一个文字生成图片的开源工具
· Java简历、面试、试用期、转正
· MySQL 10 MySQL为什么有时候会选错索引?
点击右上角即可分享
微信分享提示