P4198 楼房重建
很经典的线段树题。
计算每栋楼的斜率,答案即为求最长前缀最大值。
但是最长前缀最大值不能靠左右子区间运算得来,怎么办?
考虑去计算右子区间对答案的贡献。不妨我们将左子区间内最大值设为 \(M\),将右子区间继续分为 \(ls, rs\),设 \(ls\) 内的最大值为 \(m\),设 \(len(p)\) 为区间 \(p\) 的最长前缀最大值。
若 \(m \le M\) 那么 \(ls\) 对答案没有贡献,递归求 \(rs\)。
若 \(m > M\) 那么 \(ls\) 有部分贡献(因为有的数可能小于等于 \(M\)),那么递归求 \(ls\)。\(rs\) 的贡献就等价于对右子区间的贡献了,即为 \(len(p)-len(ls)\)(\(p\) 为右子区间)。
代码(最最最核心代码):
里头有两个小剪枝,没有影响不大。
int solve(int p, int l, int r, double x) {
if (mx[p] <= x) return 0;
if (a[l] > x) return len[p];
if (l == r) return a[l] > x;
int mid = l+r>>1;
if (mx[ls] <= x) return solve(rs, mid+1, r, x);
else return solve(ls, l, mid, x)+len[p]-len[ls];
}
其他就没了,基本是普通线段树的模板了。
#include <bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int N = 1e5+5;
int n, m, len[N<<2];
double a[N], mx[N<<2];
void pushup(int p) { mx[p] = max(mx[ls], mx[rs]); }
int solve(int p, int l, int r, double x) {
if (mx[p] <= x) return 0;
if (a[l] > x) return len[p];
if (l == r) return a[l] > x;
int mid = l+r>>1;
if (mx[ls] <= x) return solve(rs, mid+1, r, x);
else return solve(ls, l, mid, x)+len[p]-len[ls];
}
void modify(int p, int l, int r, int x, int y) {
if (l == r) {
mx[p] = y*1.0/x;
len[p] = 1;
return;
}
int mid = l+r>>1;
if (x <= mid) modify(ls, l, mid, x, y);
else modify(rs, mid+1, r, x, y);
pushup(p);
len[p] = len[ls]+solve(rs, mid+1, r, mx[ls]);
}
int main() {
cin >> n >> m;
for (int x, y; m--; ) {
cin >> x >> y;
a[x] = y*1.0/x;
modify(1, 1, n, x, y);
cout << len[1] << '\n';
}
return 0;
}