pku 3017 单调队列优化DP

来源:互联网 发布:游戏辅助软件开发 编辑:程序博客网 时间:2024/05/29 07:16

首先是方程。很容易看出来。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;
}

 

原创粉丝点击