【AcWing算法基础课】第五章 动态规划(未完待续)(3)

简介: 当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。

四、记忆化搜索

题目链接:901. 滑雪


4.1题目描述

给定一个 R 行 C 列的矩阵,表示一个矩形网格滑雪场。


矩阵中第 i 行第 j 列的点表示滑雪场的第 i 行第 j 列区域的高度。


一个人从滑雪场中的某个区域内出发,每次可以向上下左右任意一个方向滑动一个单位距离。


当然,一个人能够滑动到某相邻区域的前提是该区域的高度低于自己目前所在区域的高度。


下面给出一个矩阵作为例子:


1  2  3  4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9


在给定矩阵中,一条可行的滑行轨迹为24−17−2−1。


在给定矩阵中,最长的滑行轨迹为25−24−23−…−3−2−1,沿途共经过 25 个区域。


现在给定你一个二维矩阵表示滑雪场各区域的高度,请你找出在该滑雪场中能够完成的最长滑雪轨迹,并输出其长度(可经过最大区域数)。


输入格式


第一行包含两个整数 R 和 C。


接下来 R 行,每行包含 C 个整数,表示完整的二维矩阵。


输出格式


输出一个整数,表示可完成的最长滑雪长度。


数据范围


1≤R,C≤300,0≤矩阵中整数≤10000


输入样例:


5 5
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9


输出样例:


25


4.2思路分析

934bd8082a3765056c45cb9c94889b52_b4c19fd87274484d86c4287d09817da7.png

1)状态表示


集合:所有从 (i,j) 开始滑的路径。

属性:f[i][j]表示满足条件集合中的滑行距离的最大值。

2)状态计算


集合划分:按照从当前位置开始第一步是从上、下、左、右哪个方向开始滑行的进行分类。

计算:针对每一种状态,假设第一步走到的点是(x,y),则我们在计算f[i][j]时可以先将从(i,j)到(x,y)的距离去掉,则就变成了求从(x,y)开始滑行,可以滑行的最大距离。按照集合定义我们可知即为f[x][y]。所以,可得转移方程为 f[i][j]=max(f[i][j],f[x][y]+1)。而每一次的f[][]我们需要用dfs来求,所以我们用dfs+记忆化搜索,也相当于是dp来进行求解了。

4.3代码实现

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=310;
int dx[]={1,-1,0,0},dy[]={0,0,1,-1};   //方向数组,存储四个方向的偏移量
int f[N][N];
int h[N][N];
int r,c;
//dp(x,y)返回从(x,y)开始滑行,可以滑行的最大距离
int dp(int x,int y){
    if(f[x][y]!=-1) return f[x][y];    //如果当前状态已经被计算过,直接返回即可
    f[x][y]=1;      //否则,当前的状态的滑行距离至少为1(当前位置)
    //枚举上、下、左、右四个方向
    for(int i=0;i<4;i++){
        int a=x+dx[i],b=y+dy[i];
        //如果该方向的点在范围内,而且可以滑过去
        if(a>=1&&a<=r&&b>=1&&b<=c&&h[a][b]<h[x][y])
          f[x][y]=max(f[x][y],dp(a,b)+1);    //转移方程
    }
    return f[x][y];
}
int main(){
    cin>>r>>c;
    for(int i=1;i<=r;i++){
        for(int j=1;j<=c;j++){
            cin>>h[i][j];
        }
    }
    memset(f,-1,sizeof f);    //将每种状态初始化为-1
    int res=1;
    //枚举每个点,求出从每个点开始滑行的最大距离,然后再在其中取一个最大值即为答案
    for(int i=1;i<=r;i++){
        for(int j=1;j<=c;j++){
            res=max(res,dp(i,j));
        }
    }
    cout<<res;
    return 0;
}


五、树形DP

题目链接:285. 没有上司的舞会


5.1题目描述

Ural 大学有 N 名职员,编号为 1∼N。


他们的关系就像一棵以校长为根的树,父节点就是子节点的直接上司。


每个职员有一个快乐指数,用整数 Hi 给出,其中 1≤i≤N。


现在要召开一场周年庆宴会,不过,没有职员愿意和直接上司一起参会。


在满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员的快乐指数总和最大,求这个最大值。


输入格式


第一行一个整数 N。


接下来 N 行,第 i 行表示 i 号职员的快乐指数 Hi。


接下来 N−1 行,每行输入一对整数 L,K,表示 K 是 L 的直接上司。


输出格式


输出最大的快乐指数。


数据范围


1≤N≤6000,−128≤Hi≤127


输入样例:


7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5


输出样例:


5


5.2思路分析

1aa0fe6bf5098e49803bc48b8d60809b_ad8a4bc9a86440c6b99dbf05eb23e19e.png


1)状态表示

371c2dea4840370dc9183ec3cb06f456_e3c8aea1063240afadb7b6603035dbb2.png

集合:f[u][0]表示所有从以u为根的子树中选择,并且不选择u这个点的方案。f[u][1]表示所有从以u为根节点的子树中选,并且选择u这个点的方案。

属性:所有人快乐指数总和的最大值。

2)状态计算


集合划分:分为f[u][0]和f[u][1]。

计算:设u的每个子树的根节点为si。每次递归地对子树进行下述处理。针对f[u][0]由于不选择u这个根结点,所以每次累加的时候可以依据选上子树根节点后值的变化来选,如果选上子树根节点总值大,则就加上该条路径,否则就加上不选子树根节点的这条路径即f[u][0]+=max(f[si][0],f[si][1]);针对f[u][1]由于选了u这个根节点,所以每次不能选包含子树根结点的路径,所以f[u][1]+=f[si][0]。

5.3代码实现

#include <iostream>
#include <cstring>
using namespace std;
const int N=6010;
int happy[N];
int h[N],e[N],ne[N],idx;   //邻接表存储树
int f[N][2];
bool has_father[N];    //记录每个点是否有父结点,便于后续找到根节点
int n;
//邻接表加边
void add(int a,int b){
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void dfs(int u){
    f[u][1]+=happy[u];     //f[u][1]因为一定选u这个点,所以其初始值为结点u的快乐值
    //枚举u的每条边
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        dfs(j);     //递归求解      
        f[u][0]+=max(f[j][0],f[j][1]);   //转移方程
        f[u][1]+=f[j][0];                //转移方程
    }
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>happy[i];
    memset(h,-1,sizeof h);
    for(int i=0;i<n-1;i++){
        int l,k;
        cin>>l>>k;
        has_father[l]=k;
        add(k,l);
    }
    int root=1;
    while(has_father[root]) root++;   //寻找根节点
    dfs(root);
    cout<<max(f[root][0],f[root][1]);
    return 0;
}
目录
相关文章
|
1月前
|
算法 开发者 Python
惊呆了!Python算法设计与分析,分治法、贪心、动态规划...这些你都会了吗?不会?那还不快来学!
【7月更文挑战第10天】探索编程巅峰,算法至关重要。Python以其易读性成为学习算法的首选。分治法,如归并排序,将大问题拆解;贪心算法,如找零问题,每步求局部最优;动态规划,如斐波那契数列,利用子问题解。通过示例代码,理解并掌握这些算法,提升编程技能,面对挑战更加从容。动手实践,体验算法的神奇力量吧!
56 8
|
1月前
|
算法 Python
算法不再难!Python分治法、贪心、动态规划实战解析,轻松应对各种算法挑战!
【7月更文挑战第8天】掌握Python算法三剑客:分治、贪心、动态规划。分治如归并排序,将大问题拆解递归解决;贪心策略在每步选最优解,如高效找零;动态规划利用子问题解,避免重复计算,解决最长公共子序列问题。实例展示,助你轻松驾驭算法!**
43 3
|
5天前
|
机器学习/深度学习 算法 Java
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
这篇文章介绍了基于贪婪技术思想的Prim算法和Dijkstra算法,包括它们的伪代码描述、Java源代码实现、时间效率分析,并展示了算法的测试用例结果,使读者对贪婪技术及其应用有了更深入的理解。
算法设计(动态规划应用实验报告)实现基于贪婪技术思想的Prim算法、Dijkstra算法
|
5天前
|
算法 Java 测试技术
算法设计(动态规划实验报告) 基于动态规划的背包问题、Warshall算法和Floyd算法
这篇文章介绍了基于动态规划法的三种算法:解决背包问题的递归和自底向上实现、Warshall算法和Floyd算法,并提供了它们的伪代码、Java源代码实现以及时间效率分析。
算法设计(动态规划实验报告) 基于动态规划的背包问题、Warshall算法和Floyd算法
|
1月前
|
算法 Python
Python算法高手进阶指南:分治法、贪心算法、动态规划,掌握它们,算法难题迎刃而解!
【7月更文挑战第10天】探索Python算法的精华:分治法(如归并排序)、贪心策略(如找零钱问题)和动态规划(解复杂问题)。通过示例代码揭示它们如何优化问题解决,提升编程技能。掌握这些策略,攀登技术巅峰。
40 2
|
1月前
|
算法 程序员 Python
算法小白到大神的蜕变之路:Python分治法、贪心、动态规划,一步步带你走向算法巅峰!
【7月更文挑战第9天】探索算法之旅,以Python解锁编程高手之路。分治法如二分查找,将复杂问题拆解;贪心算法解决活动选择,每次选取局部最优;动态规划求斐波那契数列,避免重复计算,实现全局最优。每一步学习,都是编程能力的升华,助你应对复杂挑战,迈向算法大师!
29 1
|
1月前
|
存储 算法 Python
Python算法界的秘密武器:分治法巧解难题,贪心算法快速决策,动态规划优化未来!
【7月更文挑战第9天】Python中的分治、贪心和动态规划是三大关键算法。分治法将大问题分解为小问题求解,如归并排序;贪心算法每步选局部最优解,不保证全局最优,如找零钱;动态规划存储子问题解求全局最优,如斐波那契数列。选择合适算法能提升编程效率。
41 1
|
1月前
|
存储 算法 Python
震撼!Python算法设计与分析,分治法、贪心、动态规划...这些经典算法如何改变你的编程世界!
【7月更文挑战第9天】在Python的算法天地,分治、贪心、动态规划三巨头揭示了解题的智慧。分治如归并排序,将大问题拆解为小部分解决;贪心算法以局部最优求全局,如Prim的最小生成树;动态规划通过存储子问题解避免重复计算,如斐波那契数列。掌握这些,将重塑你的编程思维,点亮技术之路。
39 1
|
1月前
|
存储 算法 大数据
Python算法高手的必修课:深入理解分治法、贪心算法、动态规划,让你的代码更智能!
【7月更文挑战第9天】在Python算法学习中,分治法(如归并排序)将大问题分解为小部分递归解决;贪心算法(如货币找零)在每步选择局部最优解尝试达到全局最优;动态规划(如斐波那契数列)通过存储子问题解避免重复计算,解决重叠子问题。掌握这三种方法能提升代码效率,解决复杂问题。
32 1
|
1月前
|
算法 索引 Python
逆袭算法界!Python分治法、贪心算法、动态规划深度剖析,带你走出算法迷宫!
【7月更文挑战第8天】分治法,如快速排序,将大问题分解并合并解;贪心算法,选择局部最优解,如活动选择;动态规划,利用最优子结构避免重复计算,如斐波那契数列。Python示例展示这些算法如何解决实际问题,助你精通算法,勇闯迷宫。
23 1