题解 P1295 [TJOI2011] 书架
题意
给出一个长度为 $n$ 的序列 $h$,请将 $h$ 分成若干段,满足每段数字之和都不超过 $m$,最小化每段的最大值之和。
(~实际上就是简化题意~)。
分析
这是一个区间划分问题,自然可以想到使用 DP 解决此题。
切分($n\le10^3$)
写这种题毕竟要从暴力打起,我们用 $n^2$ 解决这个 30 分。
f[0]=0;
for(int i=1; i<=n; ++i) {
int mx=0;
f[i]=INF;
for(int j=i; j; --j) {
if(qzh[i]-qzh[j-1]>m) break;
mx=max(mx,a[j]);
f[i]=min(f[i],f[j-1]+mx);
}
}
时间复杂度:$O(n^2)$。
虽然切的是 30 分,但是却可以拿到 56 分,在开了 O2 后还可以拿到 77 分的高分,(如果是比赛已经可以放弃了)。
正解
我们显然是要优化内层的循环。
动态规划的一个重点就是继承,
令当前需要处理的节点为 $i$,
我们令 $\mathit{g}_{i,j} =f_{j-1}+ \max_{k=j}^{k \le i}a_k$,
显然 $f_i=\max_{j=1}^{j\le i} \mathit{g}_{i,j} (qzh_i-qzh_j\le m )$。
接下来就是解决在 $\mathit{g}_{i,j}$ 的转移,对于其转移,只会修改后面的 $\max_{k=j}^{k \le i}a_k$,多了一个 $a_i$,那么假如说 $a_i$ 小于原本的,那么 $\mathit{g}_{i,j}$ 就不变。
我们仔细观察 $\max_{k=j}^{k \le i}a_k$ 的变化,我们可以发现,我们的 $\max_{k=j}^{k \le i}a_k$ 是单调不增的,是如同阶梯状的。
这也就表示,我们的 $\mathit{g}_{i,j}$ 是可以用区间修改的。
归纳一下,我们想要实现:区间的查询维护答案,区间修改维护 $g$ 数组。显然啊,我们用线段树来优化我们的 DP。
为了维护最大值的变化,我们使用一个单调递减的单调栈,这样我们可以实现阶梯型的修改了。
tree.build(1,1,n+1);
f[0]=0;
tree.change(1,1,n+1,1,a[1]);
int it=1;
for(int i=1; i<=n; ++i) {
while(top&&a[st[top]]<=a[i]) {
tree.change(1,1,n+1,st[top-1]+1,st[top],a[i]-a[st[top]]);
--top;
}
while(it<=i&&qzh[i]-qzh[it-1]>m) ++it;
st[++top]=i;
f[i]=tree.query(1,1,n+1,it,i);
tree.change(1,1,n+1,i+1,f[i]+a[i+1]);
}
cout<<f[n];
时间复杂度:$O(n\log n)$。

浙公网安备 33010602011771号