记录106
cpp
#include<bits/stdc++.h>
using namespace std;
bool dp[55][1010];//第几首歌,音量
int main(){
int n,bL,mL,t;//歌曲数,开始音量,最大音量,每次改变
cin>>n>>bL>>mL;//输入
dp[0][bL]=1;//初始化dp数组
for(int i=1;i<=n;i++){//每首歌遍历
cin>>t;//输入改变音量
for(int j=mL;j>=0;j--){//开始核对音量合法性
if(j-t>=0&&dp[i-1][j-t]) dp[i][j]=1;//调低
if(j+t>=0&&dp[i-1][j+t]) dp[i][j]=1;//调高
}//本首音量根据上一首歌曲的音量来调节
}//i为歌曲数,j为音量
for(int j=mL;j>=0;j--){//找最大音量
if(dp[n][j]==1){//找到了
cout<<j;//输出
return 0;//结束程序
}
}
cout<<-1;//无法避免音量低于,或者高于最大值
return 0;//结束程序
}
题目传送门
https://www.luogu.com.cn/problem/P1877
突破口
一个吉他手准备参加一场演出。他不喜欢在演出时始终使用同一个音量,所以他决定每一首歌之前他都需要改变一次音量。在演出开始之前,他已经做好一个列表,里面写着每首歌开始之前他想要改变的音量是多少。每一次改变音量,他可以选择调高也可以调低。
音量用一个整数描述。输入文件中整数 beginLevel,代表吉他刚开始的音量,整数 maxLevel,代表吉他的最大音量。音量不能小于 0 也不能大于 maxLevel。输入中还给定了 n 个整数 c1,c2,c3,⋯,cn,表示在第 i 首歌开始之前吉他手想要改变的音量是多少。
吉他手想以最大的音量演奏最后一首歌,你的任务是找到这个最大音量是多少。
🔍 一、题目核心理解
🎯 问题描述
- 吉他手初始音量为
beginLevel - 有
n首歌,每首歌前要改变一次音量 - 第
i次改变的绝对值 为c_i(即可以 +c_i 或 -c_i) - 音量必须始终满足:
0 ≤ 音量 ≤ maxLevel - 目标 :在合法的前提下,使第 n 首歌开始时的音量最大
✅ 注意:改变发生在"每首歌开始之前",所以经过
n次调整后,得到的是最后一首歌的演奏音量
📌 关键约束
- 不能低于 0,不能高于
maxLevel - 如果任何一步无法合法调整 (即没有可行路径),输出
-1
🧠 解题思路:动态规划(可行性 DP)
这是一个典型的 状态转移可行性问题:
- 定义
dp[i][v] = true表示:在第 i 首歌开始前(即完成 i 次调整后),音量可以达到 v - 初始状态:
dp[0][beginLevel] = true - 转移:
- 从
dp[i-1][v_prev]出发 - 可以变为
v_prev + c_i(调高)或v_prev - c_i(调低) - 若结果在
[0, maxLevel]内,则标记dp[i][new_v] = true
- 从
- 最终答案:在
dp[n][v]中找最大的v(从maxLevel往下找)
💡 因为只要判断"是否可达",用 布尔型 DP 即可
代码分析
cpp
#include<bits/stdc++.h>
using namespace std;
bool dp[55][1010]; // dp[i][j]:第 i 首歌开始前能否达到音量 j
55:因为n ≤ 50,所以最多 50 首歌,i=0..501010:因为maxLevel ≤ 1000,音量范围0..1000
cpp
int main(){
int n, bL, mL, t;
cin >> n >> bL >> mL; // 读入:歌曲数、初始音量、最大音量
cpp
dp[0][bL] = 1; // 初始状态:0 次调整后,音量为 beginLevel
dp[0][v]表示还没开始调整(演出前)的状态
cpp
for(int i = 1; i <= n; i++){ // 处理第 i 首歌前的第 i 次调整
cin >> t; // 读入第 i 次要改变的音量绝对值 c_i
cpp
for(int j = mL; j >= 0; j--){ // 枚举当前可能的音量 j
⚠️ 注意:这里
j是调整后的音量(即第 i 首歌开始时的音量)
cpp
if(j - t >= 0 && dp[i-1][j - t])
dp[i][j] = 1; // 调高:上一状态是 j-t,加 t 得到 j
- 如果上一状态音量是
j - t,这次调高 t ,得到j - 需要
j - t ≥ 0(上一状态合法)
cpp
if(j + t >= 0 && dp[i-1][j + t])
dp[i][j] = 1; // 调低:上一状态是 j+t,减 t 得到 j
- 如果上一状态音量是
j + t,这次调低 t ,得到j - 注意:
j + t可能 >mL,但dp[i-1][j+t]在那种情况下本来就是false(因为之前已限制范围) j + t >= 0其实恒成立(因j≥0, t≥1),但写上无妨
✅ 这两个
if覆盖了所有可能到达j的方式
cpp
}
}
cpp
for(int j = mL; j >= 0; j--){ // 从最大音量往下找
if(dp[n][j] == 1){
cout << j;
return 0;
}
}
- 找到最大的合法最终音量,立即输出并退出
cpp
cout << -1;
return 0;
}
- 如果没有任何
dp[n][j]为 true → 无法完成所有调整而不越界 → 输出-1
🧪 样例验证(输入 #1)
cpp
3 5 10
5 3 7
过程:
- 初始:
dp[0][5] = true - 第1次(t=5):
- 可达:5+5=10,5-5=0 →
dp[1][10]=dp[1][0]=true
- 可达:5+5=10,5-5=0 →
- 第2次(t=3):
- 从10:10+3=13(>10,无效),10-3=7 →
dp[2][7]=true - 从0:0+3=3,0-3=-3(无效)→
dp[2][3]=true
- 从10:10+3=13(>10,无效),10-3=7 →
- 第3次(t=7):
- 从7:7+7=14(无效),7-7=0 →
dp[3][0]=true - 从3:3+7=10,3-7=-4(无效)→
dp[3][10]=true
- 从7:7+7=14(无效),7-7=0 →
- 最终:
dp[3][10] = true→ 输出10✅
⚠️ 关键细节说明
| 细节 | 说明 |
|---|---|
| 数组大小 | dp[55][1010] 完全覆盖 n≤50, maxLevel≤1000 |
| 初始化 | 全局 bool 数组默认为 false,只设 dp[0][bL]=true 正确 |
| 内层循环顺序 | 从 mL 到 0 无影响(因为读的是 i-1 层,写的是 i 层,无覆盖问题) |
| 边界检查 | j-t >= 0 防止负音量;j+t 虽可能超 mL,但 dp[i-1][j+t] 为 false,安全 |
总结:问题与解法对应
| 题目要求 | DP 设计 |
|---|---|
| 每次 ±c_i | 状态转移两种方向 |
| 音量 ∈ [0, maxLevel] | 转移时检查合法性 |
| 最大化最终音量 | 从高到低扫描 dp[n][*] |
| 无法完成则 -1 | 若无可达状态,输出 -1 |