Luogu 4198 楼房重建
BZOJ 2957
挺妙的题。
先把题目中的要求转化为斜率,一个点$(x, y)$可以看成$\frac{y}{x}$,这样子我们要求的就变成了一个区间内一定包含第一个值的最长上升序列。
然后把这个序列开成线段树,维护一下区间内的答案$res$和最大值$mx$,显然对于叶子结点有$mx = a_l$,$res = 1$。
$mx$的更新非常简单直接取个最大值就好了,但是$res$的更新有一些复杂,对于一个区间$[l, r]$,左儿子$[l, mid]$的值可以直接加过来,因为左儿子一定会被选到,但是右儿子的值并不那么容易计算,我们用$solve(l, r, v)$表示区间$[l, r]$内第一个值超过$v$的元素必选的最长上升序列的大小,当$l == r$的时候,只要观察$mx$是否大于$v$就可以得到答案,而$solve$函数的合并则与左儿子区间的最大值有关,具体来说:当$mx_{lc} > v$的时候,右儿子全部被选到,然后递归计算左儿子$solve(l, mid, v)$,否则递归计算右儿子。
一次合并需要访问$log$个结点,总时间复杂度$O(nlog^2n)$。
Code:
#include <cstdio> #include <cstring> using namespace std; typedef double db; const int N = 1e5 + 5; int n, qn; inline void read(int &X) { X = 0; char ch = 0; int op = 1; for(; ch > '9' || ch < '0'; ch = getchar()) if(ch == '-') op = -1; for(; ch >= '0' && ch <= '9'; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } inline db max(db x, db y) { return x > y ? x : y; } namespace SegT { int res[N << 2]; db mx[N << 2]; #define lc p << 1 #define rc p << 1 | 1 #define mid ((l + r) >> 1) int solve(int p, int l, int r, db v) { if(l == r) return (mx[p] > v); if(mx[lc] <= v) return solve(rc, mid + 1, r, v); else return solve(lc, l, mid, v) + res[p] - res[lc]; } void modify(int p, int l, int r, int x, db v) { if(l == r) { mx[p] = v; res[p] = 1; return; } if(x <= mid) modify(lc, l, mid, x, v); else modify(rc, mid + 1, r, x, v); mx[p] = max(mx[lc], mx[rc]); res[p] = res[lc] + solve(rc, mid + 1, r, mx[lc]); } } using namespace SegT; int main() { read(n), read(qn); for(int x, v; qn--; ) { read(x), read(v); modify(1, 1, n, x, (db)v / x); printf("%d\n", res[1]); } return 0; }