POj3017 dp+单调队列优化

  传送门

解题思路:

大力推公式:dp[i]=min(dp[k]+max(k+1,i)){k>=0&&k<i},max(j,i)记为max(a[h]){h>k&&h<=i},时间复杂度o(n^2)跑不动。考虑有什么冗余的决策可以优化,j到i的累加和记做sum(j,i),所以题目要保证sum(k+1,i)<=m(m为连续子序列和的上限)。

  贪心的去想:

    第一种情况如果确定max(j,i)==a[j],那么j可以作为一个可能的决策

    第二种情况如果确定max(j,i)==a[j]那么若j和i同属于一个子序列,子序列长度一定越大越好,既存在最小的k使{max(k,i)==a[j]&&sum(k,i)<=m}。至于为什么中间不能出现更优决策点,因为dp[i]>=dp[i-1]一定成立,所以越长越好。

  根据上述情况发现,决策在a上具有单调性,所以可以维护一个决策点i单调递增,a[i]单调递减的单调队列。但是这跟正常的单调队列优化dp的套路不一样,队首并不一定是最优决策,所以要同时用multiset维护dp[j]+max(j+1,i)。所以最优决策是multiset的队首与单调队列的队首其中之一。注意的是单调队列要和multiset同时维护,同增同减(恋爱的酸臭味~~)

  

#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<set>
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll dp[maxn],sum;
int s[maxn],h,t;
int num[maxn];
multiset <int> q;
int main()
{
    int n;ll k;scanf("%d%lld",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&num[i]);
        if(num[i]>k){
            printf("-1");exit(0);
        }
    }
    h=1;int id=1;
    for(int i=1;i<=n;i++){
        sum+=num[i];
        while(sum>k)    sum-=num[id++];
        if(id>i){
            printf("-1");exit(0);
        }
        while(h<=t&&num[s[t]]<num[i]){
            if(h<t)
                q.erase(dp[s[t-1]]+num[s[t]]);
            t--;
        }
        s[++t]=i;
        if(h<t){
            q.insert(dp[s[t-1]]+num[i]);
        }
        while(h<=t&&s[h]<id){
            if(h<t)
                q.erase(dp[s[h]]+num[s[h+1]]);
            h++;
        }
        dp[i]=dp[id-1]+num[s[h]];
        ll t=*q.begin();
        if(h<t&&t<dp[i])
            dp[i]=t;
    }
    printf("%lld
",dp[n]);
}
View Code

 cout和printf混用这题tle了(没用输入输出优化),求解

看了一下网上的还有下面这种算法不知道是不是数据水了,可以hack可惜没有

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
ll dp[maxn],sum[maxn];
int s[maxn],h,t;
int num[maxn];
int main()
{
    int n;ll k;scanf("%d%lld",&n,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&num[i]);sum[i]=sum[i-1]+num[i];
        if(num[i]>k){
            cout<<"-1"<<endl;return 0;
        }
    }
    h=1;
    for(int i=1;i<=n;i++){
        int id=upper_bound(sum+1,sum+1+n,sum[i]-k)-sum;
        while(sum[i]-sum[id-1]>k)    id++;
        //cout<<id<<endl;
        while(h<=t&&num[s[t]]<=num[i])    t--;
        s[++t]=i;
        while(h<=t&&s[h]<id)  h++;
        dp[i]=dp[id-1]+num[s[h]];
        for(int j=h+1;j<=t;j++)
            dp[i]=min(dp[i],dp[s[j-1]]+num[s[j]]);
    }
    cout<<dp[n]<<endl;
}
View Code

这用一个递减的排列很明显可以卡成o(n^2)~~~~

    

原文地址:https://www.cnblogs.com/r138155/p/12713841.html