【BZOJ2957】楼房重建

【题目链接】

【思路要点】

  • 令\(B_i=\frac{A_i}{i}\),问题等价于从0开始不断地找右侧第一个\(B_i\)严格大于当前位置的位置,一共可以进行多少轮。
  • 考虑用线段树解决本题。
  • 为每个节点\(root\)(对应区间\([L,R]\))记录区间最大值\(Max_{root}\)以及从左侧从区间最大值开始,一共可以在\([L,R]\)范围内进行上述过程的轮数\(val_{root}\)。
  • 假设我们已经维护好了这样的一系列信息,我们现在要支持一种询问\(query(L,R,pre)\)表示从区间左侧某一个\(B_i\)等于\(pre\)的位置开始,一共可以在\([L,R]\)范围内进行上述过程的轮数。
  • 若\(pre≥Max_{leftchild}\),那么\([L,Mid]\)内不会有任何一个数被找到,因此返回\(query(Mid+1,R,pre)\)。
  • 否则,\(val_{root}\)就是右侧会被找到的数的个数,返回\(query(L,Mid,pre)+val_{root}\)。
  • 可以发现,单次调用\(query\)的复杂度是\(O(LogN)\)的。
  • 因此,我们可以借助\(query\)在修改后维护线段树上的信息。
  • 时间复杂度\(O(N+MLog^2N)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
struct frac {int x, y; };
bool operator == (frac a, frac b) {return 1ll * a.x * b.y == 1ll * a.y * b.x; }
bool operator < (frac a, frac b) {return 1ll * a.x * b.y < 1ll * a.y * b.x; }
bool operator > (frac a, frac b) {return 1ll * a.x * b.y > 1ll * a.y * b.x; }
bool operator <= (frac a, frac b) {return 1ll * a.x * b.y <= 1ll * a.y * b.x; }
bool operator >= (frac a, frac b) {return 1ll * a.x * b.y >= 1ll * a.y * b.x; }
struct SegmentTree {
	struct Node {
		int lc, rc, val;
		frac Max;
	} a[MAXN * 2];
	int size, root, n;
	void build(int &root, int l, int r) {
		root = ++size;
		a[root].Max = (frac) {0, 1};
		a[root].val = 0;
		if (l == r) return;
		int mid = (l + r) / 2;
		build(a[root].lc, l, mid);
		build(a[root].rc, mid + 1, r);
	}
	int query(int root, int l, int r, frac pre) {
		if (l == r) return a[root].Max > pre;
		int mid = (l + r) / 2;
		if (a[a[root].lc].Max > pre) return query(a[root].lc, l, mid, pre) + a[root].val;
		else return query(a[root].rc, mid + 1, r, pre);
	}
	void update(int root, int l, int r, int mid) {
		a[root].Max = max(a[a[root].lc].Max, a[a[root].rc].Max);
		a[root].val = query(a[root].rc, mid + 1, r, a[a[root].lc].Max);
	}
	void init(int x) {
		n = x;
		root = size = 0;
		build(root, 1, n);
	}
	void modify(int root, int l, int r, int pos, frac val) {
		if (l == r) {
			a[root].Max = val;
			return;
		}
		int mid = (l + r) / 2;
		if (mid >= pos) modify(a[root].lc, l, mid, pos, val);
		else modify(a[root].rc, mid + 1, r, pos, val);
		update(root, l, r, mid);
	}
	void modify(int x, frac val) {
		modify(root, 1, n, x, val);
	}
	int query() {
		return query(root, 1, n, (frac) {0, 1});
	}
} ST;
int n, m;
int main() {
	read(n), read(m);
	ST.init(n);
	while (m--) {
		int x, y;
		read(x), read(y);
		ST.modify(x, (frac) {y, x});
		writeln(ST.query());
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值