1. 程式人生 > >Luogu 4198 樓房重建

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; }
View Code