BZOJ 2748 - [HAOI2012]音量调节 - 简单dp(题解 + 动态规划理解)

2748: [HAOI2012]音量调节

Time Limit: 3 Sec  Memory Limit: 128 MB
[Submit][Status][Discuss]

Description

一个吉他手准备参加一场演出。他不喜欢在演出时始终使用同一个音量,所以他决定每一首歌之前他都要改变一次音量。在演出开始之前,他已经做好了一个列表,里面写着在每首歌开始之前他想要改变的音量是多少。每一次改变音量,他可以选择调高也可以调低。
音量用一个整数描述。输入文件中给定整数beginLevel,代表吉他刚开始的音量,以及整数maxLevel,代表吉他的最大音量。音量不能小于0也不能大于maxLevel。输入文件中还给定了n个整数c1,c2,c3…..cn,表示在第i首歌开始之前吉他手想要改变的音量是多少。
吉他手想以最大的音量演奏最后一首歌,你的任务是找到这个最大音量是多少。

Input

第一行依次为三个整数:n, beginLevel, maxlevel。
第二行依次为n个整数:c1,c2,c3…..cn。

Output

输出演奏最后一首歌的最大音量。如果吉他手无法避免音量低于0或者高于maxLevel,输出-1。

Sample Input

3 5 10
5 3 7

Sample Output

10

HINT

1<=N<=50,1<=Ci<=Maxlevel 1<=maxlevel<=1000

0<=beginlevel<=maxlevel

思路

我们有一个初始音量,每次有个决策选择加c或减c,然后求最后一首歌的最大音量。这是最优解问题,每次决策依赖于当前状态。又随即引起状态的转移,我们很容易会想到动态规划,虽然是个很简单的题,却引起了我的思考——这是动态规划还是递推?

我们知道动态规划要满足三个特征:

  • 最优子结构:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构
  • 无后效性:对于一个状态我们不需要知道这个状态是如何得到的,即未来与过去无关
  • 有重叠子问题:子问题之间不是相互独立的,一个子问题在下一阶段的决策中可能被多次使用到

我们再来看这个问题,这个问题无后效性,并且也有重叠子问题,但是貌似没有最优子结构,所以这不是动态规划,只是个递推?于是我查了许多动态规划的资料,各种博客,书籍,最后在知乎找到我较为认可的两个回答:传送门1 传送门2

我认为网上大多数对最优子结构的描述都不够严谨准确,我认为最优子结构应该是指问题的最优解包含的子问题包含的若干状态中有最优解,也就是每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到。

如果只是由上一阶段的最优解推出,那就是贪心,动态规划应该是由上一阶段的某个或某些状态推出当前阶段的某个或某些状态,可以看作自带剪枝的暴力,它可以缩小解空间并且缩小后的解空间包含所有可能到达最优解的转移路线,本质是个DAG(有向无环图)的最短路问题,事实上各个算法不应该割裂开来思考,引用传送门1的答主的回答就是

  • 每个阶段只有一个状态->递推;
  • 每个阶段的最优状态都是由上一个阶段的最优状态得到的->贪心;
  • 每个阶段的最优状态是由之前所有阶段的状态的组合得到的->搜索;
  • 每个阶段的最优状态可以从之前某个阶段的某个或某些状态直接得到而不管之前这个状态是如何得到的->动态规划。

很显然本题一维无法解决,因为每个阶段有多个状态,一维只能存储每个阶段的一个状态,所以我们要增加一维描述,用f[i][j]表示第i首歌音量j是否可达,最后一首歌倒着遍历找到最大的可达音量即可。

代码

/*
* @Author: Admin
* @Date:   2020-04-19 18:44:51
* @Last Modified by:   Admin
* @Last Modified time: 2020-04-19 21:04:33
*/
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define fi first
#define se second
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> PII;
typedef pair<ll, ll> pll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
const int INF = 0x3f3f3f3f;
ll qpow(ll base, ll n){ll ans = 1; while (n){if (n & 1) ans = ans * base % mod; base = base * base % mod; n >>= 1;} return ans;}
ll gcd(ll a, ll b){return b ? gcd(b, a % b) : a;}
int f[60][1010];
int main()
{
	int n, s, r;
	cin >> n >> s >> r;
	f[0][s] = 1;
	int c;
	for (int i = 1; i <= n; ++ i){
		cin >> c;
		for (int j = 0; j <= r; ++ j){
			if (f[i - 1][j]){
				if (j - c >= 0) f[i][j - c] = 1;
				if (j + c <= r) f[i][j + c] = 1;
			}
		}
	}
	int ans = -1;
	for (int i = r; i >= 0; -- i) {
		if (f[n][i]){
			ans = i;
			break;
		}
	}
	cout << ans << endl;

	return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值