浅谈单调队列优化DP

匿名 (未验证) 提交于 2019-12-03 00:14:01

@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\) 跳房子

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!