P4198 楼房重建 题解

题目传送门

思路

好题。首先可以想到的是用线段树维护斜率。那答案其实就是从 \(1\) 号点开始(且必须包含 \(1\) 号点)的最长上升子序列。考虑如何 push_up。对于每一个区间维护两个数据:\(maxn_{l,r}\) 代表 \([l,r]\) 中的最大值,\(cnt_{l,r}\) 代表 \([l,r]\) 中可见楼房个数。我们会发现,\(cnt_{l,r} = cnt_{l,mid} + [mid + 1, r]\) 中的可见楼房个数。考虑用递归实现。令 \(maxl\) 为左儿子的最大值。分两种情况讨论:

  1. 如果为叶子节点,即 \(l = r\),直接判断当前这个点的斜率是否 \(> maxl\) 即可;
  2. 否则,将 \([l, r]\) 再分裂为两个区间 \([l,mid]\)\([mid +1,r]\)。如果左儿子中最大斜率 \(\le maxl\),则左儿子不会有任何贡献,在右儿子里寻找个数即可。否则,我们就在左儿子里寻找个数,右儿子中的个数就为 \(cnt_{l,r} - cnt_{l, mid}\),即 \([l,r]\) 中的个数减去左儿子的个数。

对应代码即为:

int calc(int p, int l, int r, double maxl)
{
  if (tr[p].maxn <= maxl) return 0;
  if (l == r) // 叶子节点情况
  {
    if (tr[p].maxn > maxl) return 1;
    return 0;
  }
  int mid = (l + r) >> 1;
  if (tr[p * 2].maxn < maxl) return calc(p * 2 + 1, mid + 1, r, maxl); // 左儿子没有贡献
  return calc(p * 2, l, mid, maxl) + tr[p].cnt - tr[p * 2].cnt; // 左儿子有贡献
}

这里着重解释一下右儿子中的个数为 \(cnt_{l,r} - cnt_{l, mid}\),而不是 \(cnt_{mid + 1, r}\) 的原因。因为左儿子有贡献,所以可能会将右儿子的部分楼房挡住,所以就是 \(cnt_{l,r} - cnt_{l, mid}\)

最终时间复杂度 \(\mathcal{O}(n \log n)\),可以通过此题。

代码

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;

int n, m;

struct seg_tree
{
	struct node
	{
		int cnt;
		double maxn;
	} tr[4 * N];
	int calc(int p, int l, int r, double maxl)
	{
		if (tr[p].maxn <= maxl) return 0;
		if (l == r)
		{
			if (tr[p].maxn > maxl) return 1;
			return 0;
		}
		int mid = (l + r) >> 1;
		if (tr[p * 2].maxn < maxl) return calc(p * 2 + 1, mid + 1, r, maxl);
		return calc(p * 2, l, mid, maxl) + tr[p].cnt - tr[p * 2].cnt;
	}
	void push_up(int p, int l, int r)
	{
		int mid = (l + r) >> 1;
		tr[p].maxn = max(tr[p * 2].maxn, tr[p * 2 + 1].maxn);
		tr[p].cnt = tr[p * 2].cnt + calc(p * 2 + 1, mid + 1, r, tr[p * 2].maxn);
	}
	void modify(int p, int l, int r, int k, double x)
	{
		if (l == r)
		{
			tr[p].maxn = x, tr[p].cnt = 1;
			return;
		}
		int mid = (l + r) >> 1;
		if (k <= mid) modify(p * 2, l, mid, k, x);
		if (k > mid) modify(p * 2 + 1, mid + 1, r, k, x);
		push_up(p, l, r);
	}
} ST;

int main()
{
	scanf("%d%d", &n, &m);
	for (int i = 1, x, y; i <= m; i++)
	{
		scanf("%d%d", &x, &y);
		double now = y * 1.0 / x;
		ST.modify(1, 1, n, x, now);
		printf("%d\n", ST.tr[1].cnt);
	}
	return 0;
}
posted @ 2026-01-04 17:40  lucasincyber  阅读(0)  评论(0)    收藏  举报