首先是方程。很容易看出来。dp[i] = min(dp[j]+maxsum[j+1][i])。然后这个n^2的方程就必须要优化。
其实看。对于第i个数来说,它所在段的左边界是单调不减的。那么这个就比较类似单调队列了。
还有一点经验。我发现如果dp[i]的子问题如果是类似本题的好多个dp[j]的话就可以考虑用单调队列来优化。
开始我想的是,每算出一个状态来就把dp[i]放进队列里。那么要删哪些呢?显然,由于长度i是递增的,那么i一定大于前面入队的长度。对于其后状态的影响,如果dp[i]<=dp[j],那么显然dp[j]是可以踢掉的(因为对于其后某数k,maxnum[j][k]>=maxnum[i][k],而dp[j]>=dp[i])。这样,队列里就剩下了一个长度递增,值也递增的序列。然后在更新下一个dp[i+1]的值的时候,先从队首把不符合条件的踢掉(注意这点!!),然后取一个值更新。此时,这个值是最小的。那么是不是一定是最优的呢?显然,我们设maxnum[limit[i+1]][i+1]的位置为p。那么对于从当前这个最小的状态值到此位置p这一段里,取这个最小值一定是最优的。但是越过了这个p之后呢?就不一定了。所以说我们必须依次考虑最大值,次大值,等等等等一直到i+1处的状态值。
好了,对于实现,我开始是想在这个队列里二分查找我想要的最大值的位置。但是二分查找这种东西……实在是纠结得很啊。
所以应该可以继续深入。
可以看到,这里出现了最大值!单调队列不就是维护这样的一个最值作为队首的吗??单调队列优化不一定要存状态值的呀!!
所以说我们只需要维护每一段的最大值即可。然后依次扫下去,不断更新最值,算出dp[i]。
注意一下边界的处理以及无解情况的判断。
还有,本来想到了队首的无用元素要出队,后面竟然忘记了TvT……贡献了几次WA啊…………
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 100010;
int n;
long long m;
long long num[maxn],sum[maxn];
long long dp[maxn];
/*int m;
int num[maxn],sum[maxn];
int dp[maxn];*/
int main()
{
int i,j;
while(scanf("%d%I64d",&n,&m)!=EOF)
{
sum[0] = 0;
for(i = 1;i<=n;i++)
{
scanf("%I64sd",&num[i]);
sum[i] = sum[i-1]+num[i];
}
int queue[maxn];
int head = 0,tail = 0;
queue[++tail] = 1;
head++;
int flag = 0;
int t = 0;
dp[0] = 0;
if(num[1]>m){printf("-1/n");continue;}
dp[1] = num[1];
for(i = 2;i<=n;i++)
{
while(i>t&&sum[i]-sum[t]>m)t++;
if(i==t){flag = 1;break;}
while(head<=tail&&num[i]>=num[queue[tail]])tail--;
queue[++tail] = i;
while(head<=tail&&sum[i]-sum[queue[head]-1]>m)head++;
dp[i] = dp[t]+num[queue[head]];
for(j = head;j<tail;j++)
dp[i] = min(dp[i],dp[queue[j]]+num[queue[j+1]]);
}
printf("%I64d/n",flag?-1:dp[n]);
}
return 0;
}