2013THU集训Day1 楼房重建

题意:

在x轴上有N栋房子,坐标为1~N。初始时每栋房子的高度为0(即还没建),之后有M天,每天可以修改一栋房子的高度。一个人站在原点向x轴正半轴看去,如果从原点到某栋楼楼顶的连线不与其他的楼相交,那么这人就看得到这栋楼。求每天可以看见几栋房子。N,M<=100000。


分析:

设第i栋房子的高度为a_i。如果一栋房子可以被看到,即其斜率a_i/i比其前面所有的房子的斜率都要大。那么现在问题变成了,每次修改一个数字,问此时有多少数字比之前的数字都要大。

一种比较显然的O(Nlog^2N)做法是用线段树套平衡树……但是写起来挺蛋疼。事实上我们可以只用线段树完成这个任务。

一个显而易见的结论是,这种数字的值是单调递增的。我们修改一个数只会对这个数后面的数造成影响。考虑线段树划分出来的若干线段。这里有两种情况:

1、某个线段中的最大值小于等于修改的数,那么这个线段的贡献为0,无需处理

2、否则我们将这个线段分成两个并单独考虑,如果左侧的最大值大于修改的数,那么是不影响右侧的贡献的,只需递归处理左侧;否则就变成了第一种情况

那么我们就可以用线段树来解决这个问题了……复杂度好像是O(Nlog^2N)?但是常数巨小无比,而且非常好写……


代码:(calc那里实现需要注意一下……虽然我也不知道为啥)


//BZOJ2957; rebuild (2013THU Training Day 1); Segment Tree
#include <cstdio>
#include <algorithm>
using namespace std;

inline int read() {
	static int r;
	static char c;
	r = 0, c = getchar();
	while (c < '0' || c > '9') c = getchar();
	while (c >= '0' && c <= '9') r = r * 10 + (c - '0'), c = getchar();
	return r;
}

#define N 100000
struct node {
	double v;
	int s;
} tree[N * 5];
#define ls(x) ((x) << 1)
#define rs(x) ((x) << 1 | 1)
#define s(x) (tree[x].s)
#define v(x) (tree[x].v)

int calc(int x, int l, int r, double v) {
	if (l == r) return (int)(v(x) > v);
	int mid = l + r >> 1;
	if (v(ls(x)) <= v) return calc(rs(x), mid + 1, r, v);
	return s(x) - s(ls(x)) + calc(ls(x), l, mid, v);
}

void modify(int x, int l, int r, int p, double v) {
	if (l == r) {
		v(x) = v, s(x) = 1;
		return ;
	}
	int mid = l + r >> 1;
	if (p <= mid) modify(ls(x), l, mid, p, v);
	else modify(rs(x), mid + 1, r, p, v);
	v(x) = max(v(ls(x)), v(rs(x)));
	s(x) = s(ls(x)) + calc(rs(x), mid + 1, r, v(ls(x)));
}

int n, m, x, y;

int main(int argc, char* argv[]) {
#ifdef KANARI
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	
	n = read(), m = read();
	while (m--) {
		x = read(), y = read();
		modify(1, 1, n, x, (double)y / x);
		printf("%d\n", s(1));
	}
	
	fclose(stdin);
	fclose(stdout);
	return 0;
}


你可能感兴趣的:(2013THU集训Day1 楼房重建)