这篇是搬运自己在洛谷写的题解 题目链接:\(luoguP1725\) 相信读完题就能看出这是一道简单的DP题
@TOC
状态转移
状态转移方程很容易想到。
即:
dp[ i ] = max{区间内的最大dp值}+该点的权值
然而这种算法的最坏复杂度为$O(n^2)$,而本题的数据是$1e5$级别的,这样的复杂度显然不正确,必须优化。
优化
哪一步可以优化呢?
可以发现这样转移有着冗杂的枚举区间内元素的过程。
我们又发现每次跳的区间都是一个固定长度,自然而然地想到了一个经典的问题:滑动窗口问题,即单调队列,可以让我们做到$O(n)$预处理,$O(1)$找到任意固定长度区间内的最值。
有了单调队列,我们的dp过程可以省去枚举区间内的每个元素的过程,总复杂度也由$O(n^2)$降到$O(n)$,完全没有压力。
如何实现
在代码中由注释进行讲解:
#include
using namespace std;
#define oo 0x3f3f3f3f
#define ri register int
const int N = 200005;
int n,l,r,head = 1,tail = 0,cur = 0,ans = -oo,a[N],q[N],dp[N];
//head,tail分别是单调队列的首尾指针,cur是指向当前待入队列的元素,a存权值,q为单调队列(手打)
inline int max( int a , int b ){return a > b ? a : b; }//手打max
template
inline void read(T &res){
static char ch;T flag = 1;
while( ( ch = getchar() ) < '0' || ch > '9' ) if( ch == '-' ) flag = -1;
res = ch - 48;
while( ( ch = getchar() ) >= '0' && ch <= '9' ) res = res * 10 + ch - 48;
res *= flag;
}//快读
int main()
{
read( n );read( l );read( r );
for( ri i = 0 ; i <= n ; i++ ) read( a[ i ] );
for( ri i = 1 ; i <= n ; i++ ) dp[ i ] = -oo;//初始化
for( ri i = 1 ; i <= n ; i++ ){
//由于一个点可到达的区间为[i+l,i+r],因此可以到达一个点的区间为[i-r,i-l],注意不要越界
while( i - cur >= l ){//让能够入队的结点入队
while( head <= tail && dp[ cur ] >= dp[ q[ tail ] ] ) tail--;
//由于单调队列是单调递减的,所以我们要删除队尾那些dp值小于待入队结点dp值的结点
q[ ++tail ] = cur++;//入队
}
while( head <= cur && i - q[ head ] > r ) head++;
//如果队列的头已经不再界限内,则不可能再更新当前
if( head <= tail )//注意,当队列不为空时才可以转移
dp[ i ] = dp[ q[ head ] ] + a[ i ];
}
//因为题目说只要下一步的位置编号大于N就算到达对岸
//所以最后得出答案,为区间[n-r+1,n]中的dp最大值
for( ri i = n ; i >= n - r + 1 ; i-- )
ans = max( ans , dp[ i ] );
printf( "%d\n" , ans );
return 0;
}
如果嫌这道题太简单,可以尝试\(P3957\) 跳房子。