传送门
解题思路:
大力推公式: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同时维护,同增同减(恋爱的酸臭味~~)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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]); }
cout和printf混用这题tle了(没用输入输出优化),求解
看了一下网上的还有下面这种算法不知道是不是数据水了,可以hack可惜没有
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; }
这用一个递减的排列很明显可以卡成o(n^2)~~~~