练习17,小y拼楼梯【动态规划DP/贪心】

题源:E-⼩y拼楼梯 - 问题 - USCOJ(PvP)

题目描述

⾃从⼩y上次学会计算有多少种爬楼梯的⽅式后,又枯燥了起来。现在她⾃⼰拼接⾃⼰想要的楼梯,于是她就开始⾃⼰脑补拼接楼梯了。她在想,⾃⼰拥有⽆限个分别为 a1,a2,a3……ak 阶的⼩楼梯,她想拼出⼀个 N 阶的楼梯最少需要多少块?

例如⼩y现在有分别为 1,2,4 阶的⼩楼梯,想要拼出 10 阶的楼梯⾄少需要 3 块⼩楼梯,分别是 4 阶的⼩楼梯使⽤ 2 块,2 阶的⼩楼梯使⽤ 1 块,即 4+4+2=10

但是⼩y现在想破了脑袋也没想到怎么计算最少值,你能帮帮他吗?
如果⽆法拼出 N 阶则输出 "-_-!!"

输入

第⼀⾏包含两个正整数 N, k    N表⽰⼩y最要拼接的楼梯阶数,表⽰⼩楼梯的个数。

第⼆⾏包含 k 个正整数 a1,a2,a3……ak(ai < aj,其中i < j),表⽰第 i 个⼩楼梯的阶数。

输出

如果能拼接出来,则输出最少需要的⼩楼梯块数。

否则输出 "-_-!!" ,(不输出引号)。

对于30%的数据:

1e9≤n≤1e16k≤10 ,且这个范围的数据对于所有ai(i>1) ,都有 a[i] 是 a[i-1] 的倍数。

对于另外 70% 的数据中:
⼀半(35%)的数据: N≤1000,k≤5
全部(70%)的数据: N≤100000,k≤20

对于所有数据 a[i]≤N

思路

对30%的数据,因为a[i] 是 a[i-1] 的倍数,所以用贪心的方法即最优解法

而对剩下70%的数据,则需要动态规划做

代码如下:

#include<bits/stdc++.h>
using namespace std;
#define lint long long

lint n,k,f=0;
lint a[100],d[100010]={0};
lint dp(int m){ //动态规划
    int flag=0; //flag来标记无法被任何已有小楼梯所拼出的阶数
    if(m==0) {f=1; return 0;} //f=1表示能拼出目标楼梯数
    if(d[m]) return d[m]; //记忆化搜索
    for(int i=1;i<=k;i++){ //从k个小楼梯中递归找出全局最优解
        if(m-a[i]>=0){
            flag=1;
            if(d[m]==0) d[m]=dp(m-a[i])+1;
            else d[m]=min(d[m],dp(m-a[i])+1);
        }
    }
    if(flag==0) return 1e9;
    return d[m];
}
int main(){
    cin >> n >> k;
    for(int i=1;i<=k;i++) cin >> a[i];

    if(n>=1e9){ //贪心
        lint ans=0,i=k;
        while(n>0 && i>0){
            ans+=n/a[i];
            n%=a[i];
            i--;
        }
        if(n!=0) cout << "-_-!!";
        else cout << ans;
        return 0;
    }

    lint ans=dp(n);
    if(f==1) cout << ans;
    else cout << "-_-!!";
    return 0;
}

此题动态规划还有一种递推方法(从0阶递推到n阶小楼梯):

#include<bits/stdc++.h>
using namespace std;
#define lint long long

lint a[100],d[100010]={0};
int main(){
    lint n,k;
    cin >> n >> k;
    for(int i=1;i<=k;i++) cin >> a[i];
    d[0]=0;
    for(int i=1;i<=n;i++){
        lint cost=1e16;
        for(int j=1;j<=k;j++)
            if(i-a[j]>=0) cost=min(cost,d[i-a[j]]+1);
        d[i]=cost;
    }
    for(int i=1;i<=n;i++) cout << d[i] << endl;
    cout << d[n];
    return 0;
}

<think>嗯,用户现在想了解动态规划的知识体系,可能用于学习或教学。我需要先回顾一下之前的对话。在之前的回答中,用户问的是蓝桥杯算法在数学建模中的应用,我详细列举了动态规划、图论算法等的应用场景,并且引用了多个参考资料,比如引用[1]、[3]、[4]等。现在用户的新问题更侧重于动态规划本身的知识体系,包括基本概念、应用场景、算法实现及优化。 首先,用户的需求是系统的知识体系,可能他正在准备学习动态规划或者需要教授相关内容。需要确保回答结构清晰,覆盖基本概念、核心要素、应用场景、实现步骤、优化策略以及教学建议。同时,用户提供的引用中有多个关于动态规划的内容,比如引用[1]提到最优子结构和重叠子问题,引用[2]和[3]解释了动态规划的基本思想和适用场景,引用[4]讨论了优化方法,引用[5]补充了重叠子问题的重要性。这些引用内容需要整合到回答中,确保信息的准确性和全面性。 接下来,我要组织内容的结构。通常,知识体系的讲解可以从基本概念开始,然后到核心要素,再讲应用场景,接着是算法实现步骤,之后是优化策略,最后是教学或学习的建议。需要确保每个部分都有足够的细节,比如基本概念中要定义动态规划,区分它与分治法的不同;核心要素包括最优子结构、状态转移方程等;应用场景可以举经典问题如背包问题、最短路径等;实现步骤可能需要分步骤解释,并给出伪代码或Python示例;优化策略要覆盖空间优化、状态压缩等;教学建议则要强调理解和实践的重要性。 需要注意的是,用户可能希望有具体的例子来辅助理解,比如在讲解状态转移方程时,用斐波那契数列或背包问题作为例子。同时,用户提供的引用中提到的一些优化方法,如滚动数组、记忆化搜索等,需要详细说明。此外,引用[5]提到动态规划通过空间换时间,这一点在优化策略中也要强调。 可能还需要检查是否有遗漏的重要概念,比如动态规划贪心算法的区别,或者在应用场景中的实际案例,如资源分配、路径规划等。同时,用户之前的问题涉及到数学建模,所以在应用场景中可以适当结合数学建模的例子,比如库存管理、生产计划等,以保持连贯性。 最后,要确保语言通俗易懂,适合学习和教学使用。可能需要避免过于专业的术语,或在必要时进行解释。例如,在讲解状态转移方程时,可以用数学公式展示,同时用自然语言解释其含义。此外,代码示例要简洁明了,比如用Python实现斐波那契数列的记忆化搜索,或者背包问题的动态规划解法。 总结一下,回答的结构应该是:基本概念→核心要素→应用场景→实现步骤→优化策略→教学建议,每个部分都结合引用中的内容,并给出具体例子和代码示例。同时,确保各部分之间的逻辑连贯,覆盖动态规划的完整知识体系,满足用户学习和教学的需求。</think>### 动态规划知识体系详解 动态规划(Dynamic Programming, DP)是解决**多阶段决策最优化问题**的核心算法范式。以下从知识体系构建角度,结合引用文献[1][2][3][4][5]的系统论述,提供面向**学习/教学**的完整框架: --- #### 一、基本概念与数学本质 1. **形式化定义** - 通过**状态空间**描述问题:设状态集合$S$,决策集合$A$ - 状态转移方程:$s_{t+1} = f(s_t, a_t)$,其中$a_t \in A(s_t)$ - 目标函数:$V(s_0) = \max_{a_0,...,a_T} \sum_{t=0}^T r(s_t,a_t)$ 2. **与分治法对比** | 维度 | 动态规划 | 分治法 | |--------------|------------------------|----------------------| | 子问题关系 | 重叠且依赖(引用[5]) | 独立无重叠 | | 计算方式 | 记忆化存储 | 递归分解 | | 典型复杂度 | $O(n^2)$ | $O(n \log n)$ | 3. **核心数学特征**(引用[1][5]) - **最优子结构**:全局最优解包含局部最优解 - **重叠子问题**:不同决策路径导致重复计算 - **无后效性**:未来状态仅与当前状态有关 --- #### 二、核心要素与算法框架 1. **三大基本要素**(引用[2][5]) ```mermaid graph TD A[动态规划三要素] --> B[状态定义] A --> C[状态转移方程] A --> D[边界条件] ``` 2. **五步实现法** - **Step1 问题结构化**:将原问题分解为阶段决策过程 *示例:背包问题中按物品序号划分阶段* - **Step2 状态变量设计** - 选择原则:满足无后效性、包含决策信息 *示例:最长公共子序列中的$dp[i][j]$表示X前i项与Y前j项的LCS长度* - **Step3 状态转移方程** $$ dp[i][j] = \begin{cases} dp[i-1][j-1] + 1, & X[i]=Y[j] \\ \max(dp[i-1][j], dp[i][j-1]), & X[i] \neq Y[j] \end{cases} $$ - **Step4 边界初始化** *示例:斐波那契数列中$dp[0]=0, dp[1]=1$* - **Step5 结果回溯路径** ```python # 背包问题最优解回溯 def trace_back(dp, weights, values): n, W = len(weights), len(dp)-1 res = [] w = W for i in range(n, 0, -1): if dp[i][w] != dp[i-1][w]: res.append(i-1) w -= weights[i-1] return res[::-1] ``` --- #### 三、应用场景分类 1. **经典问题类型** | 问题类型 | 状态空间维度 | 典型例题 | |-------------------------|--------------|-----------------------| | 线性DP | 1D/2D | 最长上升子序列 | | 区间DP | 2D | 矩阵链乘法优化 | | 树形DP | 树节点相关 | 二叉树最大路径和 | | 状态压缩DP | 位运算编码 | 旅行商问题(TSP) | | 概率DP | 概率分布 | 随机游走期望计算 | 2. **工业级应用场景**(引用[1][3]) - **资源分配**:多周期生产计划(2021数学建模C题) - **路径优化**:带时间窗的物流配送路径规划 - **金融决策**:期权定价的贝尔曼方程求解 --- #### 四、优化技术体系 1. **空间复杂度优化**(引用[4]) - **滚动数组**:将$O(n^2)$空间降为$O(n)$ ```python # 斐波那契数列优化 def fib(n): if n < 2: return n a, b = 0, 1 for _ in range(2, n+1): a, b = b, a + b return b ``` - **状态压缩**:使用位运算表示状态集合 *示例:TSP问题中用n位二进制表示城市访问状态* 2. **时间复杂度优化** - **决策单调性**:利用四边形不等式优化区间DP - **斜率优化**:将转移方程转化为凸包维护问题 $$ \text{原始方程: } dp[i] = \min_{j<i}\{ dp[j] + (S_i - S_j)^2 \} $$ $$ \text{转化形式: } y_j = dp[j] + S_j^2, x_j = S_j $$ 3. **混合优化策略**(引用[4]) - **记忆化搜索+剪枝**:避免无效状态扩展 - **动态规划贪心结合**:先用贪心缩小状态空间 --- #### 五、教学/学习建议 1. **认知路径设计** ```mermaid graph LR A[理解基本概念] --> B[掌握经典模型] B --> C[练习变式问题] C --> D[学习优化技巧] ``` 2. **典型训练路线** - **初级阶段**:斐波那契数列→爬楼梯问题→背包问题 - **中级阶段**:最长公共子序列→编辑距离→股票买卖问题 - **高级阶段**:状态压缩DP→斜率优化→概率DP 3. **常见误区警示** - **错误1**:混淆贪心DP适用场景(引用[2]) *反例:部分背包问题可用贪心,但0-1背包必须用DP* - **错误2**:状态设计冗余导致维度灾难 *修正方案:分析决策变量最小充分集* --- #### 六、知识扩展方向 1. **理论延伸** - 贝尔曼方程与强化学习的联系 - 动态规划与变分法的关系 2. **工程实践工具** - **OR-Tools**:Google优化工具包中的DP模块 - **Pyomo**:Python数学建模库支持DP建模 --- ### 相关问题 1. 如何判断一个问题是否具有最优子结构性质? 2. 动态规划在处理连续型状态变量时有哪些特殊技巧? 3. 哪些类型的优化问题不适合使用动态规划
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ILECY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值