20190630模拟赛B(单调队列优化dp)

。dp无疑了其实。

在考场上,我写了一个错解,但是数据小都能过,只是会爆空间,考场上想着怎么用滚动数组优化来着。。。。把错解的方程列出来吧

for(int i=1;i<=n;i++)
{
    for(int j=0;j<=k;j++)
    {
        if(j!=0)
        dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]+a[i]);//dp[i][j]表示前i个里,第j个选不选
        else
        dp[i][j]=ans;
        ans=max(dp[i][j],ans);
    }
}

很明显错的了。但是回家发现离正解也差得不远了。我一开始已经想到了前缀和,但是不知道哪里出了点问题。

总结错误原因:空间炸了。观察方程,其实除了的小问题在这里:我明明可以把一整段加起来,但是却用了单个的加,这就导致了时空的炸裂。

于是改造dp数组:还是前i个点,但是第二个改造为j为断点,所以dp表示前i个点在j断时的最大值,方程:
dp[i]=max(dp[i],dp[j-1]+a[j]+...+a[i]);

dp类似一个前缀和吧。可以把后面一大串的a[j]+...+a[i]用前缀和维护,就变成了
dp[i]=max(dp[i],dp[j-1]+sum[i]-sum[j]);

所以,dp里面的值只和一个变量有关,所以可以使用(我及其不熟练的)单调队列进行维护。

#include<bits/stdc++.h>
using namespace std;
const int maxn=100010;
int n,m;
int a[maxn];
int sum[maxn];
int dp[maxn];
int que[maxn];//数组模拟队列
int d[maxn];//值
int h=0,t=1;

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        sum[i]=sum[i-1]+a[i];//前缀和
    }
    for(int i=1;i<=n;i++)
    {
        d[i]=dp[i-1]-sum[i];//首先,队尾是当前值
        while(h<=t&&d[que[t]]<d[i])t--;//代入方程式,判断最优解,弹掉不合适的
        que[++t]=i;//放入一个可能最优的值
        while(h<=t&&que[h]<i-m)h++;//再更新
        dp[i]=d[que[h]]+sum[i];//更新状态
    }
    printf("%d",dp[n]);
    return 0;
}

估计有很多讲的不好不对,我自己都还有点懵呢,希望大佬指正。

(我恨dp)

原文地址:https://www.cnblogs.com/ajmddzp/p/11111986.html