• 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)~~~~

        

  • 相关阅读:
    053335
    053334
    053333
    053332
    053331
    053330
    053329
    053328
    053327
    053326
  • 原文地址:https://www.cnblogs.com/r138155/p/12713841.html
Copyright © 2020-2023  润新知