@TOC
这篇是搬运自己在洛谷写的题解 题目链接:\(luoguP1725\) 相信读完题就能看出这是一道简单的DP题
状态转移
状态转移方程很容易想到。
即:
dp[ i ] = max{区间内的最大dp值}+该点的权值
然而这种算法的最坏复杂度为$O(n^2)$,而本题的数据是$1e5$级别的,这样的复杂度显然不正确,必须优化。
优化
哪一步可以优化呢?
可以发现这样转移有着冗杂的枚举区间内元素的过程。
我们又发现每次跳的区间都是一个固定长度,自然而然地想到了一个经典的问题:滑动窗口问题,即单调队列,可以让我们做到$O(n)$预处理,$O(1)$找到任意固定长度区间内的最值。
有了单调队列,我们的dp过程可以省去枚举区间内的每个元素的过程,总复杂度也由$O(n^2)$降到$O(n)$,完全没有压力。
如何实现
在代码中由注释进行讲解:
#include<cstdio> 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<class T> 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\) 跳房子。