题目大意:给一个长度为n的序列a,要你将其分成若干段,在满足每段和不超过m的情况下,让每段中的最大值之和最小,求最小值。
容易想到动态规划,令f[i]表示把前i个数分段所得的最小值
f[i]=min{f[j]+max (j+1<=k<=i){a[k]}} (a[j+1]+a[j+2]+......+a[i]<=m)
时间复杂度o(n2),我们考虑如何优化。
考虑单调队列,由于f[i]一定是单调不降的,
对于任意两个连续决策j-1,j(0<=j-1<j<i且a[j+1]+a[j+2]+......+a[i]<=m),我们分两种情况:
情况1:若(a[j]+a[j+2]+......+a[i]>m)
此时j-1不符合条件,j有用,优
情况2:若(a[j]+a[j+2]+......+a[i]<=m)
当 存在 f[j-1]+max(j<=k<=i){a[k]} <= f[j]+max(j+1<=k<=i){a[k]}
说明j-1更优,且随着i的增大,j-1更容易满足小于i,所以j为无用决策,应删除。
所以当且仅当 f[j-1]+max(j<=k<=i){a[k]} > f[j]+max(j+1<=k<=i){a[k]} ,j有用.
因为f[j-1]<=f[j]
所以 max(j<=k<=i){a[k]} > max(j+1<=k<=i){a[k]}
所以 a[j]=max(j<=k<=i){a[k]}
综上:若j更优,除了满足(a[j+1]+a[j+2]+......+a[i]<=m)
还应当满足两个条件之一
条件一 a[j]+a[j+2]+......+a[i]>m
条件二 a[j]=max(j<=k<=i){a[k]}
条件一容易处理,考虑条件二,维护一个决策点j单调递增,a[j]单调递减的队列即可。
至于max (j+1<=k<=i){a[k]}如何得到,其实就是队列下一个元素的a值。
但注意到维护的队列对f[j]+max (j+1<=k<=i){a[k]}没有单调性,所以建立一个数据结构,如平衡树set,存f[j]+max (j+1<=k<=i){a[k]},
以便快速得到。(数据太水,直接暴力扫队列竟然还要快一些)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<set> 6 #define ll long long 7 using namespace std; 8 int f[100005],a[100005],q[100005]; 9 int n; 10 ll m,sum[100005]; 11 multiset<int> s; 12 int main() 13 { 14 int i,j; 15 scanf("%d%lld",&n,&m); 16 int L=1,r=0,k=0;//k为队列区间起始位置L的前一个位置 (维护条件2) 17 for(i=1;i<=n;i++)scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i]; 18 for(i=1;i<=n;i++) 19 { 20 if(a[i]>m){printf("-1");return 0;} 21 while(sum[i]-sum[k]>m)k++;//维护k 22 while(L<=r&&q[L]<=k) 23 { 24 if(L<r)s.erase(f[q[L]]+a[q[L+1]]);//删去 区间和大于m的候选项 25 L++; 26 } 27 while(L<=r&&a[q[r]]<=a[i]) 28 { 29 if(L<r)s.erase(f[q[r-1]]+a[q[r]]);//维护a[]单调递减 30 r--; 31 } 32 q[++r]=i; 33 if(L<r)s.insert(f[q[r-1]]+a[i]); 34 f[i]=f[k]+a[q[L]];//条件1更新f 35 if(L<r)f[i]=min(f[i],*s.begin());//条件2更新f 36 } 37 printf("%d",f[n]); 38 return 0; 39 }