洛谷 P4198 楼房重建
作者:互联网
一道线段树维护区间前缀最大值个数的好题。
思路
易得连接 \((0,0),(i,H_i)\) 的线段斜率为 \(s_i = \frac{H_i}{i}\)。则题要求的就是满足 \(i \in [1,n], s_i > \max\limits_{j=1}^{i-1} s_j\) 的 \(i\) 的个数。考虑线段树维护。
线段树上每个结点维护当前区间 \([l,r]\) 的斜率 \(\max\) 和只考虑 \([l,r]\) 的前缀最大值个数 \(cnt\),即不考虑 \([1,l-1]\) 对 \([l,r]\) 的影响。\(\max\) pushup
是很容易的,对左、右儿子取 \(\max\) 即可。考虑 \(cnt\) 怎么 pushup
。
引入一个新函数 calc(rt, l, r, x)
,它返回区间 \([l,r]\) 内,假设 \([1,l-1]\) 的前缀最大值为 \(x\),\([l,r]\) 内的 \(cnt\)。
- 当 \(rt\) 为叶子结点,若 \(x < s_i\) 返回 \(1\),否则返回 \(0\)。显然。
- 当 \(rt\) 为非叶子结点,考虑左右子树两部分贡献分别计算。若 \(x < \max_{ls}\),则左子树的贡献为
calc(ls, l, mid, x)
,右子树的贡献为 \(cnt_{rt} - cnt_{ls}\)。因为在右子树中相当于前缀 \(\max\) 为左子树的 \(\max\),因此 \(cnt\) 的贡献直接相减即可得到。若 \(x \ge \max_{ls}\),则左子树的贡献为 \(0\),右子树的贡献为calc(rs, mid + 1, r, x)
。
现在可以回头解决 pushup
的问题了:\(cnt_{rt} = cnt_{ls_{rt}} + \operatorname{calc}(rs,mid+1,r,\max_{ls})\)。左子树的 \(cnt\) 可以直接继承,因为左子树的区间为当前子树的区间的前缀;右子树的 \(cnt\) 就利用 \(\mathrm{calc}\) 计算。由于是只考虑 \([l,r]\) 区间的,所以 \(x\) 设为 \(\max_{ls}\) 就行。
时间复杂度 \(O(m \log^2 n)\)。
代码
code
/*
p_b_p_b txdy
AThousandMoon txdy
AThousandSuns txdy
hxy txdy
*/
#include <bits/stdc++.h>
#define pb push_back
#define fst first
#define scd second
using namespace std;
typedef long long ll;
typedef long double ldb;
typedef pair<ll, ll> pii;
const int maxn = 100100;
ll n, m, a[maxn];
struct treenode {
ldb mx;
int cnt;
} tree[maxn << 2];
int calc(int rt, int l, int r, ldb x) {
if (l == r) {
return (x < (ldb)a[l] / l ? 1 : 0);
}
int mid = (l + r) >> 1;
if (x < tree[rt << 1].mx) {
return calc(rt << 1, l, mid, x) + tree[rt].cnt - tree[rt << 1].cnt;
} else {
return calc(rt << 1 | 1, mid + 1, r, x);
}
}
void pushup(int x, int l, int r) {
int mid = (l + r) >> 1;
tree[x].mx = max(tree[x << 1].mx, tree[x << 1 | 1].mx);
tree[x].cnt = tree[x << 1].cnt + calc(x << 1 | 1, mid + 1, r, tree[x << 1].mx);
}
void update(int rt, int l, int r, int x) {
if (l == r) {
tree[rt].mx = (ldb)a[l] / l;
tree[rt].cnt = 1;
return;
}
int mid = (l + r) >> 1;
if (x <= mid) {
update(rt << 1, l, mid, x);
} else {
update(rt << 1 | 1, mid + 1, r, x);
}
pushup(rt, l, r);
}
void solve() {
scanf("%lld%lld", &n, &m);
while (m--) {
ll x, y;
scanf("%lld%lld", &x, &y);
a[x] = y;
update(1, 1, n, x);
printf("%d\n", tree[1].cnt);
}
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}
标签:rt,左子,cnt,洛谷,楼房,P4198,ls,max,calc 来源: https://www.cnblogs.com/zltzlt-blog/p/16484860.html