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)