洛谷P1848 书架

好,我一直觉得书架是splay,而后发现还有个优化DP的书架。妃的书架数组

蓝书和PPT上面都讲了,应该比较经典吧。ide

题意:函数

有n个物品,每一个都有宽,高。优化

把它们分红若干段,使得每段的最大值的总和最小。且每段的总宽度不超过L。spa

n <= 100000code

解:blog

首先有个很显然的DP是f[i] = min(f[j] + max(j + 1, i)), sum[i] - sum[j] <= L队列

而后如何优化呢?由于有max函数(非线性)因此难以单调队列。把决策打出来发现不单调。机关用尽???get

发现当i固定的时候,max(j + 1, i)是一段一段单减的。这样至关于能肯定状态转移方程中的后者。it

再看前者,f[j]这个东西,实际上是单调不减的。证实:把f[j - 1]按照f[j]的方式划分便可<=f[j]。

怎么利用呢?朴素的想法是把每个数前面最大的数用单调栈求出来,记为to[]数组。

那么每次转移的时候跳to[]便可。能够发如今区间(to[i], i)之间的转移都不优于to[i]。

这里忽视了一个小问题,有个宽度限制在这里。个人解决办法是用pos记录最先的一个可以转移过来的位置。pos的维护显然是线性的。

这个好想好写的东西最坏复杂度仍是n²,递增序列就能卡掉,然而交上去有90分......

来考虑正解。咱们能不能每次不跳to[]链,而是更快的求出最小值来转移呢?

很容易(困难)想到用堆维护。对于失效的转移用延迟删除法。

问题就只剩如何判断转移失效了。

若是转移的j < pos,显然不行。此外,若是j不在以i开头的to链上,也是不行的。

有个朴素的想法是用数组维护是否在to链上,即每次把i前面比i小的舍去,可是又会被递增卡成n²。

仔细思考,发现以i开头的to链就是单调栈在处理到i时的栈内元素。

因而咱们一边DP一边维护单调栈,只需判断j是在否在栈中便可。

至此,时间复杂度优化为nlogn,能够经过此题。

记得开long long

 1 #include <cstdio>
 2 #include <algorithm>
 3 #include <queue>
 4 #define mp std::make_pair
 5 
 6 typedef long long LL;
 7 const int N = 100010;
 8 
 9 LL f[N], sum[N];
10 int st[N][25], pow[N], n, to[N], p[N], top;
11 bool in_stk[N];
12 std::priority_queue<std::pair<LL, int> > Q;
13 
14 inline void STinit() {
15     int j = 1, lm = 0;
16     while((1 << lm) <= n) {
17         while(j < (1 << (lm + 1)) && j <= n) {
18             pow[j] = lm;
19             j++;
20         }
21         lm++;
22     }
23     for(int j = 1; j < lm; j++) {
24         for(int i = 1; i + (1 << j) - 1 <= n; i++) {
25             st[i][j] = std::max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
26         }
27     }
28     return;
29 }
30 
31 inline int getmax(int l, int r) {
32     int t = pow[r - l + 1];
33     return std::max(st[l][t], st[r - (1 << t) + 1][t]);
34 }
35 
36 int main() {
37     int L;
38     scanf("%d%d", &n, &L);
39     for(int i = 1; i <= n; i++) {
40         scanf("%d%lld", &st[i][0], &sum[i]);
41         sum[i] += sum[i - 1];
42         f[i] = 1ll << 60;
43     }
44 
45     STinit();
46     st[0][0] = 0x7f7f7f7f;
47     to[0] = -1;
48 
49     int pos = 0;
50     for(int i = 1; i <= n; i++) {
51         while(st[i][0] >= st[p[top]][0]) {
52             in_stk[p[top]] = 0;
53             top--;
54         }
55         to[i] = p[top];
56         p[++top] = i;
57         in_stk[i] = 1;
58 
59         Q.push(mp(-1 * (f[to[i]] + getmax(to[i] + 1, i)), i));
60 
61 
62         while(sum[i] - sum[pos] > L) {
63             pos++;
64         }
65         while((!Q.empty()) && (to[Q.top().second] < pos || (!in_stk[Q.top().second]))) {
66             Q.pop();
67         }
68         if(!Q.empty()) {
69             f[i] = -1 * Q.top().first;
70         }
71         f[i] = std::min(f[i], f[pos] + getmax(pos + 1, i));
72     }
73     /*for(int i = 1; i <= n; i++) {
74         printf("%lld ", f[i]);
75     }*/
76     printf("%lld", f[n]);
77     return 0;
78 }
AC代码

可是还有不少能够改进的地方。

好比to这个数组能否舍去?ST表能否舍去?我是懒得优化了T_T

相关文章
相关标签/搜索