bzoj2442[Usaco2011 Open]修剪草坪——单调队列优化
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2442
考虑记录前 i 个、末尾 j 个连续选上的最大值。发现时空会爆。
又发现大量的转移形如 dp[ i ][ j ] = dp[ i-1 ][ j-1 ]+a[ i ]。
再结合自己求答案要遍历 j = i ~ j - k ,就觉得可以只记录一个 i ,在 i 到 i - k 的范围强制选后面连续的一段,并让转移来的dp的后面一个强制不选。
这样在 i 到 i-k 的范围里在强制选的后缀之前一定有一个不选的,符合条件。
实现需要一点技巧。
1.dp[i-1]表示 i-1 之后的那个格子不选。所以应该先fx+=a[i],再dp[i-1]-=fx,这样dp[i-1]没有加上a[i]的值,符合设定。
2.如果从开始就一直选,岂不是要从dp[ -1 ]转移来才行?所以把所有角标+1,就行了!原来的dp[0]变成dp[1],不用特殊管。
#include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std; const int N=1e5+5; int n,k,q[N]; ll a[N],dp[N],h,t,fx; int rdn() { int ret=0;char ch=getchar(); while(ch>'9'||ch<'0')ch=getchar(); while(ch>='0'&&ch<='9')(ret*=10)+=ch-'0',ch=getchar(); return ret; } ll rdl() { ll ret=0;char ch=getchar(); while(ch>'9'||ch<'0')ch=getchar(); while(ch>='0'&&ch<='9')(ret*=10)+=ch-'0',ch=getchar(); return ret; } int main() { n=rdn();k=rdn(); h=1;q[++t]=0; for(int i=2;i<=n+1;i++) { a[i]=rdl(); while(h<=t&&i-q[h]>k+1)h++; fx+=a[i];dp[i-1]-=fx; while(h<=t&&dp[i-1]+fx>=dp[q[t]]+fx)t--; q[++t]=i-1; dp[i]=dp[q[h]]+fx; // printf("dp[%d]=%lld dp[%d]=%lld fx=%lld\n",i,dp[i],q[h],dp[q[h]],fx); } printf("%lld",dp[n+1]); return 0; }