P4198 楼房重建

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;
}
posted @ 2023-12-09 10:56  123wwm  阅读(7)  评论(0编辑  收藏  举报