题目
把一个长度为nnn的数列分成若干段,使每段的总和不超过mmm,并且每段的最大值和最小
分析
按照朴素的方法,那么f[i]=min0≤j<i(∑k=j+1iAk≤m){f[j]+maxj<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]}},然而可以发现答案需要维护一个单调递减的AjA_jAj队列,那问题是答案怎么办,虽然朴素可以水过,但是还是用了平衡树去解决,时间复杂度O(nlogn)O(nlogn)O(nlogn)(然而单调队列+朴素,最坏O(n2)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');
}

博客围绕将长度为n的数列分段,使每段总和不超m的问题展开。分析了朴素方法,其状态转移方程为f[i]=min0≤j<i(∑k=j+1iAk≤m){f[j]+maxj<k≤i{a[k]}},需维护单调递减的Aj队列,还提及用平衡树解决,给出了时间复杂度。最后展示了代码。
275

被折叠的 条评论
为什么被折叠?



