这道题通过预处理 确定状态 的遍历答案,从而优化复杂度.
我们来考虑一下暴力做法:
维护一个二元组
(
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);
}
}