http://www.lydsy.com/JudgeOnline/problem.php?id=2957
Description
小A的楼房外有一大片施工工地,工地上有N栋待建的楼房。每天,这片工地上的房子拆了又建、建了又拆。他经常无聊地看着窗外发呆,数自己能够看到多少栋房子。
为了简化问题,我们考虑这些事件发生在一个二维平面上。小A在平面上(0,0)点的位置,第i栋楼房可以用一条连接(i,0)和(i,Hi)的线段表示,其中Hi为第i栋楼房的高度。如果这栋楼房上任何一个高度大于0的点与(0,0)的连线没有与之前的线段相交,那么这栋楼房就被认为是可见的。
施工队的建造总共进行了M天。初始时,所有楼房都还没有开始建造,它们的高度均为0。在第i天,建筑队将会将横坐标为Xi的房屋的高度变为Yi(高度可以比原来大—修建,也可以比原来小—拆除,甚至可以保持不变—建筑队这天什么事也没做)。请你帮小A数数每天在建筑队完工之后,他能看到多少栋楼房?
Input
第一行两个正整数N,M
接下来M行,每行两个正整数Xi,Yi
Output
M行,第i行一个整数表示第i天过后小A能看到的楼房有多少栋
分块或者线段树皆可。首先把求出所有楼的斜率。
分块的话, 维护每个块内从起点开始的单调上升序列,每次更新只需要维护某一个块,查询的时候,在当前块内的单调上升序列中找到第一个比上一个块内最高楼高的楼,设为x,可以用二分实现查找,那么当前块的单调上升序列中在x之后的都可以看到。依次查找完毕即可。
用线段树的话,更新了一个位置后,对这个位置左边的答案是没有影响的,只对右边有影响,把右边的区间划分成左子区间和右子区间,当左子区间的最大值小于等于更新值的时候,对答案没有贡献,直接去右子区间查询,否则的话,右子区间对于答案的贡献是不变的,变的只有左子区间,去左子区间查询
分块:
#include
using namespace std;
const int N = 100000 + 10, M = 1000 + 10;
int L[N], R[N], pos[N];
int sz, block;
double a[N];
double b[M][M];
int b_sz[N];
void reset(int x)
{
b_sz[x] = 0;
for(int i = L[x]; i <= R[x]; i++)
if(!b_sz[x] || a[i] > b[x][b_sz[x]]) b[x][++b_sz[x]] = a[i];
}
void init(int n)
{
block = (int)sqrt(n * log(n) / 2.0);
//block = (int)sqrt(n);
sz = n / block;
if(n % block) sz++;
for(int i = 1; i <= n; i++) pos[i] = (i-1) / block + 1;
for(int i = 1; i <= sz; i++)
{
L[i] = (i-1) * block + 1;
R[i] = i * block;
}
R[sz] = n;
for(int i = 1; i <= sz; i++) reset(i);
}
void update(int x, int y)
{
a[x] = 1.0 * y / x;
reset(pos[x]);
}
int query()
{
int ans = 0;
double maxx = 0.0;
for(int i = 1; i <= sz; i++)
{
int idx = upper_bound(b[i] + 1, b[i] + 1 + b_sz[i], maxx) - b[i];
if(idx != b_sz[i] + 1) ans += b_sz[i] - idx + 1, maxx = b[i][b_sz[i]];
}
return ans;
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
init(n);
int x, y;
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &x, &y);
update(x, y);
printf("%d\n", query());
}
return 0;
}
线段树:
#include
using namespace std;
const int N = 100000 + 10, INF = 0x3f3f3f3f;
struct node
{
int l, r, ans;
double val;
}tr[N*4];
void build(int l, int r, int k)
{
tr[k].l = l, tr[k].r = r;
tr[k].ans = 0, tr[k].val = 0;
if(l == r) return;
int mid = (l + r) >> 1;
build(l, mid, k<<1);
build(mid + 1, r, k <<1|1);
}
int calc(double val, int k)
{
if(tr[k].l == tr[k].r) return tr[k].val > val;
if(tr[k<<1].val <= val) return calc(val, k<<1|1);
else return tr[k].ans - tr[k<<1].ans + calc(val, k<<1);
}
void update(int x, double val, int k)
{
if(x == tr[k].l && x == tr[k].r)
{
tr[k].ans = 1;
tr[k].val = val;
return;
}
int mid = (tr[k].l + tr[k].r) >> 1;
if(x <= mid) update(x, val, k << 1);
else update(x, val, k << 1|1);
tr[k].val = max(tr[k<<1].val, tr[k<<1|1].val);
tr[k].ans = tr[k<<1].ans + calc(tr[k<<1].val, k<<1|1);
}
int main()
{
int n, m;
scanf("%d%d", &n, &m);
build(1, n, 1);
int x, y;
for(int i = 1; i <= m; i++)
{
scanf("%d%d", &x, &y);
update(x, 1.0 * y / x, 1);
printf("%d\n", tr[1].ans);
}
return 0;
}