IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> 树形DP练习 -> 正文阅读

[数据结构与算法]树形DP练习

树的最长路径

题目链接:树的最长路径
分析:我们需要在树上DP,状态表示这一方面是比较困难的,我们要找最长的一条路径,那我们随意画一棵树
在这里插入图片描述
代码实现:

#include<iostream>
using namespace std;
const int N=1e4+10,M=2*N;
int n,h[N],w[M],ne[M],idx=1,e[M],ans;
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int dfs(int u,int father){
    int d1=0,d2=0;//d1记录最长的边,d2记录第2长的边
    for(int i=h[u];i;i=ne[i]){//遍历所有出边
        int j=e[i];
        if(j==father)   continue;//如果是它的父亲,就跳过,这里放止往回搜
        int d=dfs(j,u)+w[i];//搜出以j为根节点的子树中最长的加上w[i]
        if(d>=d1){//更新最长边和第二长边
            d2=d1;
            d1=d;
        }
        else if(d>d2){//更新第二长边
            d2=d;
        }
    }
    ans=max(ans,d1+d2);//更新答案
    return d1;//返回最长的边
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);//加两条边
        add(b,a,c);
    }
    dfs(1,0);//第二个参数随意传一个没用到的结点,第一个结点随意,因为不管从哪个结点开始,都可以转化为一棵树
    printf("%d\n",ans);
    return 0;
}

树的中心

题目链接:树的中心
分析:这是一道很好的树形DP题,我们可以随便选取一个点,向上遍历所有点,再向下遍历所有点,遍历的途中更新答案,详细讲解见代码。
代码实现:
代码中的细节还是比较多的

#include<iostream>
using namespace std;
const int N=1e4+10,M=2*N,INF=1e9;
int n,h[N],e[M],ne[M],w[M],idx=1;
bool is_leaf[N];
int d1[N],d2[N],p[N],up[N];//d1[i]表示从第i点往下走的最长长度,d2表示第二长长度,p表示向下的最长路径经过了哪一点(记录最近的一个点即可),up表示向上走的最长路
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
int dfs_d(int u,int father){//向下搜的话和上题差不多
    d1[u]=d2[u]=0;
    for(int i=h[u];i;i=ne[i]){
        int j=e[i];
        if(j==father)   continue;
        int d=dfs_d(j,u)+w[i];
        if(d>=d1[u]){
            d2[u]=d1[u];
            d1[u]=d;
            p[u]=j;
        }
        else if(d>=d2[u]) d2[u]=d;
    }
    return d1[u];
}
int dfs_u(int u,int father){//向上走
    for(int i=h[u];i;i=ne[i]){
        int j=e[i];
        if(j==father)   continue;
        //这里当u是1时,up[u]=0,但这并不影响转移,因为一定去掉最长向下路后走拐向第二长一定是最长的
        if(p[u]==j) up[j]=max(d2[u],up[u])+w[i];//如果从u往下走的最长路经过了j,那么j点往上走的最长路就是向上或者拐弯走向第二长向下路的路径
        else up[j]=max(up[u],d1[u])+w[i];//否则只需从拐向最长向下路和向上走选一个
        dfs_u(j,u);
    }
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    dfs_d(1,0);//向下
    dfs_u(1,0);//向上
    int res=d1[1];//注意,本题中1号结点遍历到所有点都相当于向下走了
    for(int i=2;i<=n;i++){//其它点要在向上和向下最长中取个最大值再和res取个最小值
        res=min(res,max(up[i],d1[i]));
    }
    cout<<res<<endl;
    return 0;
}

数字转换

题目链接:数字转换
分析:这道题还是比较有意思的,我们把所有能连的边都预处理出来并连上,然后就需要求最长的一条路径,回到了第一题。需要注意的是,本题可能会出现多棵树的情况,即不一定所有点都连通,因此我们对每个点都搜一遍。
代码实现:

#include<iostream>
using namespace std;
const int N=5e4+10;
int idx=1,h[N],ne[N],e[N],sum[N];
int d1[N],d2[N],res;
bool st[N];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
    if(d1[u])   return ;//如果搜过就直接返回最长的
    st[u]=1;//搜过了就标记一下
    for(int i=h[u];i;i=ne[i]){
        int j=e[i];
        dfs(j);
        if(d1[j]+1>d1[u])   d2[u]=d1[u],d1[u]=d1[j]+1;
        else if(d1[j]+1>d2[u])  d2[u]=d1[j]+1;
    }
    res=max(res,d1[u]+d2[u]);
}
int main(){
    int n;cin>>n;
    for(int i=1;i<=n;i++){//先处理出所有数的约数之和
        for(int j=2;j<=n/i;j++){//因为约数不包含自身,所有j从2开始
            sum[i*j]+=i;   
        }
    }
    for(int i=2;i<=n;i++){//加边从2开始,否则会加一条从0到1的边
        if(sum[i]<i){
            add(sum[i],i);//从小的数往大的树连一条边,我们不用反向搜,所以建立一条边即可
        }
    }
    for(int i=1;i<=n;i++){//从最小的点开始搜
        if(!st[i])//如果没搜过
         dfs(i);
    }
    cout<<res;
    return 0;
}

二叉苹果树

题目链接:二叉苹果树
分析:这道题还是比较easy的,感觉到一股01背包味?再品品,感觉到一股区间DP味?或许这就是算法的美妙之处吧(你中有我,我中有你)。
代码实现:

#include<iostream>
using namespace std;
int n,q;
const int N=110,M=2*N;
int dp[N][N];//dp[i][j]表示以i为根节点的子树,保留j根树枝剩余的最大苹果数
int h[N],ne[M],w[M],idx=1,e[M];
void add(int a,int b,int c){
    e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void dfs(int u,int father){
    for(int i=h[u];i;i=ne[i]){
        if(e[i]==father)   continue;
        dfs(e[i],u);
        for(int j=q;j;j--){//枚举以u为根节点保留的树枝数量
            for(int k=0;k<j;k++){//枚举以该子节点为根节点的子树保留的树枝数量
                dp[u][j]=max(dp[u][j],dp[e[i]][k]+dp[u][j-k-1]+w[i]);
            }
        }
    }
}
int main(){
    cin>>n>>q;
    for(int i=1;i<n;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);//建双向边
        add(b,a,c);
    }
    dfs(1,0);
    cout<<dp[1][q];
    return 0;
}

战略游戏

题目链接:战略游戏
分析:一股子状态机模型味,就按照那个模型写吧,详见代码。
代码实现:

#include<iostream>
#include<cstring>
using namespace std;
const int N=1510;
int n;
int h[N],e[N],ne[N],idx=1;
int dp[N][2];//dp[i][0]表示在i处不放时的最小值,1时放置
bool vis[N];
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u){
    dp[u][0]=0,dp[u][1]=1;
    for(int i=h[u];i;i=ne[i]){
        int j=e[i];
        dfs(j);
        dp[u][0]+=dp[j][1];//若u结点不放置,子节点必须放置
        dp[u][1]+=min(dp[j][0],dp[j][1]);//若u结点放置,子节点可以放可以不放
    }
}
int main(){
    while(scanf("%d",&n)!=-1){
        memset(h,0,sizeof h);
        idx=1;
        memset(vis,0,sizeof vis);
        for(int i=1;i<=n;i++){
            int a,b;
            scanf("%d:(%d)",&a,&b);
            while(b--){
                int c;
                scanf("%d",&c);
                add(a,c);
                vis[c]=true;//不是根节点的标记一下
            }
        }
        int root=0;
        while(vis[root])    root++;//找出根节点
        dfs(root);
        printf("%d\n",min(dp[root][0],dp[root][1]));
    }
    
    return 0;
}

皇宫看守

题目链接:皇宫看守
分析:注意,本题和上题不一样,上题是观察边,本题是观察点,但其实思路都差不多,详见代码。
代码实现:

#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1510;
int n;
int h[N], w[N], e[N], ne[N], idx;
int f[N][3];//f[i][0]表示第i点被它的父节点观察到,f[i][1]表示被它的子节点看住,f[i][2]表示被自己看住
bool st[N];
void add(int a, int b){
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u){
    f[u][2] = w[u];
    int sum = 0;
    for (int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        dfs(j);
        f[u][0] += min(f[j][1], f[j][2]);//如果此节点被父节点看着,那么它的子节点必须找儿子或者自己看着
        f[u][2] += min(min(f[j][0], f[j][1]), f[j][2]);//如果此节点自己看着,那么它的子节点可以随意
        sum += min(f[j][1], f[j][2]);//sum求f[u][1]时会用,此状态下所有子节点都必须依靠自己或儿子,累加起来
    }
    f[u][1] = 1e9;//对于f[u][1],我们要选一个最好的子节点,因此初始化为很大的数,然后取最小的情况的值
    for (int i = h[u]; ~i; i = ne[i]){//枚举所有子节点,该子节点放置上守卫,则总价钱要减去min(f[j][1], f[j][2])再加上f[j][2]
        int j = e[i];
        f[u][1] = min(f[u][1], sum - min(f[j][1], f[j][2]) + f[j][2]);
    }
}
int main(){
    cin >> n;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ ){
        int id, cost, cnt;
        cin >> id >> cost >> cnt;
        w[id] = cost;
        while (cnt -- ){
            int ver;
            cin >> ver;
            add(id, ver);
            st[ver] = true;//非根结点标记下
        }
    }
    int root = 1;
    while (st[root]) root ++ ;//找出根节点
    dfs(root);
    cout << min(f[root][1], f[root][2]) << endl;
    return 0;
}

树形DP的题感觉还都是很有意思的,还要在洛谷上多刷刷题!

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-02-06 14:03:51  更:2022-02-06 14:03:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 18:21:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码