Luogu P4198 楼房重建

problem

这道题通过预处理 确定状态 的遍历答案,从而优化复杂度.

我们来考虑一下暴力做法:
维护一个二元组 ( k , c n t ) (k,cnt) (k,cnt),表示当前斜率最大值和前面一共能看到多少个点.
当我们扫描到点 ( x , y ) (x,y) (x,y)时,若 y x > k \dfrac y x > k xy>k,则用 ( y x , c n t + 1 ) (\dfrac y x,cnt+1) (xy,cnt+1)更新答案.
单次复杂度 Θ ( n ) \Theta(n) Θ(n).

单点修改,全局查询让我们想到线段树.
我们尝试用线段树上二分来处理.
m x [ x ] mx[x] mx[x]为线段树 x x x节点下的最大斜率, l c , r c lc,rc lc,rc分别为左右节点.

显然:(我们可以根据区间最大斜率剪枝)

  • m x [ l c ] ≤ k mx[lc]\le k mx[lc]k,直接递归到右子树.
  • 否则,我们应该递归完左子树,得到 k = m x [ l c ] k=mx[lc] k=mx[lc],再去递归右子树.

当然这样的复杂度是 O ( n ) O(n) O(n).

但是, k = m x [ l c ] k=mx[lc] k=mx[lc]这是一个确定的状态,我们可以考虑预处理出 ( m x [ l c ] , 0 ) (mx[lc],0) (mx[lc],0)到右子树的遍历结果.
这样每次求解是 O ( log ⁡ n ) O(\log n) O(logn),修改复杂度为 O ( log ⁡ 2 n ) O(\log ^2 n) O(log2n).

int n, m; 

struct P {
	int x,y;
	P(int a=inf,int b=0) {x=a; y=b;}
	bool operator <(P b) const {
		return 1LL*y*b.x < 1LL*b.y*x;
	}
	bool operator >(P b) const {
		return 1LL*y*b.x > 1LL*b.y*x;
	}
} mx[N<<2], now;

struct rec {
	P x; int y;
	rec(P a=P(),int b=0) {x=a; y=b;}
	rec operator +(rec b) const {
		if(x < b.x) return rec(b.x,y+b.y);
		return *this;
	}
} g[N<<2], ans;

#define lc (x<<1)
#define rc (x<<1|1)
#define mid ((l+r)/2)
#define Ls lc,l,mid
#define Rs rc,mid+1,r

rec calc(int x,int l,int r,rec st) {
	if(l == r) return st + g[x];
	if(mx[lc] > st.x) return calc(Ls,st) + g[x];
	else return calc(Rs,st);
}

void change(int x,int l,int r,int pos) {
	if(l == r) {
		mx[x] = now;
		g[x] = rec(now, 1);
		return ;
	}
	if(pos <= mid) change(Ls,pos);
	else change(Rs,pos);
	mx[x] = max(mx[lc], mx[rc]);
	g[x] = calc(Rs, rec(mx[lc], 0));
}

void query(int x,int l,int r) {
	if(l == r) {ans = ans + g[x]; return ;}
	if(mx[lc] > ans.x) query(Ls), ans = ans + g[x];
	else query(Rs);
}

void solve() {
	qr(n); qr(m);
	while(m--) {
		qr(now.x); qr(now.y);
		change(1,1,n,now.x);
		ans=rec();
		query(1,1,n);
		pr2(ans.y);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Infinite_Jerry

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值