题目
把一个长度为 n n n的数列分成若干段,使每段的总和不超过 m m m,并且每段的最大值和最小
分析
按照朴素的方法,那么 f [ i ] = m i n 0 ≤ j < i ( ∑ k = j + 1 i A k ≤ m ) { f [ j ] + m a x j < k ≤ i { a [ k ] } } f[i]=min_{0\leq j<i(\sum_{k=j+1}^iA_k\leq m)}\{f[j]+max_{j<k\leq i}\{a[k]\}\} f[i]=min0≤j<i(∑k=j+1iAk≤m){f[j]+maxj<k≤i{a[k]}},然而可以发现答案需要维护一个单调递减的 A j A_j Aj队列,那问题是答案怎么办,虽然朴素可以水过,但是还是用了平衡树去解决,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)(然而单调队列+朴素,最坏 O ( n 2 ) O(n^2) O(n2)比平衡树快)
代码
#include <cstdio>
#include <set>
typedef long long ll; std::multiset<ll>uk;
int n,a[100005],head,tail,t,q[100005];
ll sum,dp[100005],m; bool flag;
int in(){
int ans=0; char c=getchar();
while (c<48||c>57) c=getchar();
while (c>47&&c<58) ans=ans*10+c-48,c=getchar();
return ans;
}
void print(ll ans){if (ans>9) print(ans/10); putchar(ans%10+48);}
int main(){
n=in(); scanf("%lld",&m);
t=1; tail=-1;
for (register int i=1;i<=n&&!flag;i++){
sum+=(a[i]=in());
while (sum>m) sum-=a[t++];//找到满足答案的j
if (t>i) flag=1;//已经不可能了
while (head<=tail&&a[q[tail]]<=a[i]){//维护队尾
if (head<tail) uk.erase(dp[q[tail-1]]+a[q[tail]]);
tail--;
}
q[++tail]=i;//入队
if (head<tail) uk.insert(dp[q[tail-1]]+a[q[tail]]);
while (q[head]<t){
if (head<tail) uk.erase(dp[q[head]]+a[q[head+1]]);
head++;
}
dp[i]=dp[t-1]+a[q[head]];
if (head<tail&&dp[i]>*(uk.begin())) dp[i]=*(uk.begin());
}
if (flag) putchar('-'),putchar('1');
else if (dp[n]) print(dp[n]); else putchar('0');
return !putchar('\n');
}