题意:给定序列,每次修改一个值,求前缀最大值的个数。
解:线段树经典应用。
每个节点维护最大值和该区间前缀最大值个数。
发现我们不用下传标记,只需要合并区间。
需要实现一个函数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 }