P1877 [HAOI2012] 音量调节

记录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..50
  • 1010:因为 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
  • 第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
  • 第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
  • 最终:dp[3][10] = true → 输出 10

⚠️ 关键细节说明

细节 说明
数组大小 dp[55][1010] 完全覆盖 n≤50, maxLevel≤1000
初始化 全局 bool 数组默认为 false,只设 dp[0][bL]=true 正确
内层循环顺序 mL0 无影响(因为读的是 i-1 层,写的是 i 层,无覆盖问题)
边界检查 j-t >= 0 防止负音量;j+t 虽可能超 mL,但 dp[i-1][j+t]false,安全

总结:问题与解法对应

题目要求 DP 设计
每次 ±c_i 状态转移两种方向
音量 ∈ [0, maxLevel] 转移时检查合法性
最大化最终音量 从高到低扫描 dp[n][*]
无法完成则 -1 若无可达状态,输出 -1
相关推荐
dragen_light1 小时前
1.ROS2-Install
c++·python·ros
Gary Studio2 小时前
基于PMSM理论研究加实践
算法
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题】【Java基础篇】第9题:HashMap根据key查询元素的时间复杂度是多少
java·开发语言·数据结构·后端·面试·哈希算法·哈希表
不知名的老吴2 小时前
编程初体验之句柄的概念及使用示例
c++
木子墨5162 小时前
LeetCode 热题 100 精讲 | 矩阵与图论进阶篇:矩阵置零 · 螺旋矩阵 · 旋转图像 · 搜索二维矩阵 II · 岛屿数量 · 腐烂的橘子
c++·算法·leetcode·矩阵·力扣·图论
Ailan_Anjuxi2 小时前
【附jupyter源码】使用长短期记忆网络(LSTM)实现一个小说写作AI——以训练《西游记》为例
人工智能·算法
stolentime2 小时前
线段树套?——洛谷P7312 [COCI 2018/2019 #2] Sunčanje题解
c++·算法·图论·洛谷
wayz112 小时前
Day 12:支持向量机(SVM)原理与实践
算法·机器学习·支持向量机
EverestVIP2 小时前
c++ 的terminate()函数
c++