洛谷P4198 楼房重建

题意:给定序列,每次修改一个值,求前缀最大值的个数。

解:线段树经典应用。

每个节点维护最大值和该区间前缀最大值个数。

发现我们不用下传标记,只需要合并区间。

需要实现一个函数int ask([l r] lm)求出区间[l r]中前一个数是lm时前缀最大值个数。

那么当lm >= large[ls]时,return ask([mid r] lm)

这个很好理解,左子区间的所有数都不会成为前缀最大值。

当lm < large[ls]时,return ask([l mid] lm) + (sum[o] - sum[ls])

这个注意,不是sum[rs]因为sum[rs]的意义是从0开始,而这个的前面会有large[ls]挡着,所以是sum[o] - sum[ls]

修改的时候先一路到底把large值改了。然后return的时候把沿途区间都更新。

具体来说就是sum[o] = ask([l r] 0)...等等,好像有问题。

lm < large[ls]的时候,求值是要调用sum[o]的,这不就循环调用导致出错了吗?

所以写成sum[o] = sum[ls] + ask([mid r] large[ls])即可。

本题不用建树。需要建树的时候就跟修改类似的写法即可。

 1 #include <cstdio>
 2 #include <algorithm>
 3 
 4 const int N = 100010;
 5 
 6 double a[N], large[N << 2];
 7 int n, sum[N << 2];
 8 
 9 int ask(int l, int r, int o, double lm) {
10     if(l == r) {
11         return (lm < a[r]);
12     }
13     int mid = (l + r) >> 1;
14     if(lm > large[o << 1]) {
15         return ask(mid + 1, r, o << 1 | 1, lm);
16     }
17     else {
18         return sum[o] - sum[o << 1] + ask(l, mid, o << 1, lm);
19     }
20 }
21 
22 void change(int p, double v, int l, int r, int o) {
23     if(l == r) {
24         large[o] = v;
25         sum[o] = 1;
26         return;
27     }
28     int mid = (l + r) >> 1;
29     if(p <= mid) {
30         change(p, v, l, mid, o << 1);
31     }
32     else {
33         change(p, v, mid + 1, r, o << 1 | 1);
34     }
35     large[o] = std::max(large[o << 1], large[o << 1 | 1]);
36     sum[o] = sum[o << 1] + ask(mid + 1, r, o << 1 | 1, large[o << 1]);
37     return;
38 }
39 
40 int main() {
41     int m;
42     scanf("%d%d", &n, &m);
43     for(int i = 1, x, y; i <= m; i++) {
44         scanf("%d%d", &x, &y);
45         a[x] = (double)(y) / x;
46         change(x, a[x], 1, n, 1);
47         printf("%d
", sum[1]);
48     }
49 
50     return 0;
51 }
AC代码
原文地址:https://www.cnblogs.com/huyufeifei/p/10325023.html