P1725 琪露诺 题解

洛谷博客链接:https://www.luogu.com.cn/article/jelykwpn

温馨提示:这是一篇使用 set 优化 DP 的题解。

背景:前段有一场 ABC 的 D 题,如果使用 set 便可以很快地通过,但是身边的人有写 ST 表的,有写单调队列的,甚至还有写线段树的。再次拿到这道被称为单调队列优化 DP 的板子,我脑海中第一个出现的竟是使用 set 优化,于是便诞生了这篇题解。

d p i dp_i dpi 为到达第 i i i 个格子所获得的最大冰冻指数,初始 d p 0 dp_0 dp0 0 0 0 d p 1 ∼ d p n dp_1\sim dp_n dp1dpn 为负无穷。

由于第 i i i 个格子可以跳到 i + l ∼ i + r i+l\sim i+r i+li+r 个格子,所以 d p i dp_i dpi 可以由 d p i − r ∼ d p i − l dp_{i-r}\sim dp_{i-l} dpirdpil 转移而来。易得转移: d p i = max ⁡ j = i − r i − l d p j + a i dp_i=\max\limits_{j=i-r}^{i-l}dp_j+a_i dpi=j=irmaxildpj+ai

显然上面转移是 O ( n 2 ) O(n^2) O(n2) 的,考虑如何优化。发现 d p i dp_{i} dpi d p i − r ∼ d p i − l dp_{i-r}\sim dp_{i-l} dpirdpil 转移而来, d p i + 1 dp_{i+1} dpi+1 d p i − r − 1 ∼ d p i − l − 1 dp_{i-r-1}\sim dp_{i-l-1} dpir1dpil1 转移而来,有关区间只向右移动了一位,且我们只关心有关区间的最大值。

考虑用 set 维护有关区间,枚举到 i i i 时,set 维护的便是 d p i − r ∼ d p i − l dp_{i-r}\sim dp_{i-l} dpirdpil 这些值,从 set 中取最大值进行转移即可。

枚举到 i i i 时,在 set 中加入 d p i − l dp_{i-l} dpil,删去 d p i − r − 1 dp_{i-r-1} dpir1。单次操作时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

总时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)。虽然没有单调队列 O ( n ) O(n) O(n) 优秀,但比单调队列更加好写,且仍能轻松通过本题。

一些注意事项

  • d p i dp_i dpi 有可能出现相同的,因此我开的是 multiset
  • 删去 d p i − r − 1 dp_{i-r-1} dpir1 时,应写作 s.erase (s.find (dp[i - r - 1]))
  • 统计答案应从 d p n − r + 1 ∼ d p n dp_{n-r+1}\sim dp_n dpnr+1dpn 中取最大值。
  • set 中最大值为 *s.rbegin ()

代码:

#include <bits/stdc++.h>
#define int long long
#define INF 1e9 
using namespace std;
typedef long long LL;

const int N = 1e6 + 10;
int n, l, r, ans = -INF;
int a[N], dp[N];
multiset <int> s;

signed main ()
{
	ios::sync_with_stdio (false);
	cin.tie (0); cout.tie (0);
	
	cin >> n >> l >> r;
	for (int i = 0; i <= n; i++) cin >> a[i];
	memset (dp, -0x3f, sizeof dp);
	dp[0] = 0;
	for (int i = l; i <= n; i++) {
		if (i > r) s.erase (s.find (dp[i - r - 1]));
		s.insert (dp[i - l]); // 调整有关区间
		dp[i] = *s.rbegin () + a[i]; // 更新 dp 值
	}
	for (int i = n; i > n - r; i--) ans = max (ans, dp[i]); // 统计答案
	cout << ans;
	return 0;
}
  • 30
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值