某岛

… : "…アッカリ~ン . .. . " .. .
December 17, 2012

Luogu P4198. 楼房重建

Brief description:

。。维护一个区间。。每个位置有一个楼房。。。问一个人站在 (0, 0) 位置。。抬头最多能看到多少楼房。。
。。(。支持单点修改。

Analysis:

前略,填过的清橙 OJ。
如何能 O(1) 时空 的判断一个节点是否是叶子节点?

const int N = int(1e5) + 9;

namespace Segment_Tree {
#define lx (x<<1)
#define rx (lx|1)
#define ml (l+r>>1)
#define mr (ml+1)
#define lc lx, l, ml
#define rc rx, mr, r
#define root 1, 1, n
    const int NN = 4*N;
    DB mx[NN]; int jp[NN];
    int a, b, n;

    int Jmp(int x, int l, int r, DB h){
        if (l < r) return mx[lx] < h ? Jmp(rc, h) : jp[x] - jp[lx] + Jmp(lc, h);
        return h < mx[x];
    }

    void upd(int x, int l, int r){
        mx[x] = max(mx[lx], mx[rx]);
        jp[x] = jp[lx] + Jmp(rc, mx[lx]);
    }

    void Modify(int x, int l, int r) {
        if (l == r) {
            mx[x] = (DB) b / a;
            jp[x] = 1;
        } else {
            if (a < mr) Modify(lc);
            else Modify(rc);
            upd(x,l,r);
        }
    }
} using namespace Segment_Tree;


int main() {

#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif

    RD(n); Rush {
        RD(a, b); Modify(root);
        OT(jp[1]);
    }
}

———— UPD 2021/09/10 ————

(。这个题线段树的做法有点厉害。。。以下直接贴 Seter 题解。。。

半年前和主席讨论过类似的问题,得出的结论是只能sqrtnlgn,再怎么优化也只能sqrtn。。
结果居然有线段树做法。。
首先我们把每个房屋的Y除以X,再在最左边加个0,这样的话,答案就是从0开始,每次往右边跳到严格比它大的第一个数上,直到不能跳为止,总共跳了几次。。
比如得出来是0 2 1.5 2.333,那就是跳了2次,答案就是2。
当时只想出了分块。。问了一些神犇他们都不肯好好帮忙想。。于是就搁置了。。
标程是一种线段树做法。。
首先注意到,在一段中跳,最后必定在最大的那个数上停下来。
每个点代表一个区间,维护两个东西:这个区间的最大数和这个区间的最左边加个0后从0开始往右跳能跳几次。

const int _N = 100009, N = _N * 4;
int lc[N], rc[N]; DB mm[N]; int jp[N]; // 这个区间的最大数和这个区间的最左边加个 0 后从 0 开始往右跳能跳几次。 
int nn; int xx, yy; DB slop;
int n, root;
.. .

然后每个节点再支持一个操作:假设最左边来了个x,在这个区间中,从 x 开始往右跳能跳几次。
这个操作有哪些用处呢?
第一就是x=0时,得出的答案恰好就是这个点中要维护的那个东西。
第二就是,我可以合并两个相邻区间的从0开始跳的答案了!因为我必定是从0开始跳到左儿子的最大数上,然后再从左儿子的最大数开始往右儿子跳。

void Upd(int x){
    mm[x] = max(mm[lx], mm[rx]);
    jp[x] = jp[lx] + Jmp(rx, mm[lx]);
}
.. .

则整个区间的答案就是,左儿子从0开始跳的答案(这个直接维护了),加上右儿子从左儿子最大数(这个也直接维护了)开始跳的答案(这个递归下去算)。
由于每次只递归右儿子,所以每个点算答案还是lgn的,这样就可以lg^2n修改了。
—————————————————————————————————————————–
那怎么才能实现这个操作呢?
我们发现,如果x比左儿子的最大数还要大,那么左儿子就是没有用的,直接返回右儿子的答案就可以了。
否则呢?难道要返回左儿子从x开始跳的答案加上右儿子从左儿子最大数开始跳的答案吗?可是这样两边都要递归了,复杂度就坏掉了。。
我们再来看看上面提到的合并区间的方法:
“则整个区间的答案就是,左儿子从0开始跳的答案(这个直接维护了),加上右儿子从左儿子最大数(这个也直接维护了)开始跳的答案(这个递归下去算)。”
换句话说,右儿子从左儿子最大数开始跳的答案,就是整个区间从0开始跳的答案减去左儿子从0开始跳的答案,而这两个答案都直接维护了!
所以这也只需要递归一个儿子即可。

int Jmp(int x, DB h){
    if (lx) return mm[lx] <= h ? Jmp(rx, h) : jp[x] - jp[lx] + Jmp(lx, h);
    return h < mm[x];
}
.. .

整体复杂度是lg^2n的。

高端线段树
。高端线段树。(堆式存储。。

External link:

https://www.luogu.com.cn/problem/P4198