bzoj 2957 楼房重建 线段树

题面

题目传送门

解法

分块当然是可以做的啦,但是就是比较慢……
考虑一种线段树的做法
维护区间斜率的最大值 mx m x 和这段区间的答案 ans a n s
现在问题是如何通过两个子区间的合并求得 ans a n s
显然,在区间合并的时候,我们只需要考虑右儿子的答案就行了
假设我们现在有一个函数 solve(k,mx) s o l v e ( k , m x ) 表示在线段树的点 k k 上,斜率最大值为mx的情况下答案是多少
显然,如果左儿子的最大值不大于 mx m x ,那么答案就是右儿子的答案
否则,右儿子的答案一定是被左儿子限制的,但是这个答案已经被计算过了,所以答案为 solve(lsonk,mx)+anskanslsonk s o l v e ( l s o n k , m x ) + a n s k − a n s l s o n k
为什么后面不是 ansrsonk a n s r s o n k 呢??
因为在右儿子的答案不一定对应的是整个区间的答案
举个例子来解释这个问题吧:
假设当前区间中所有数为为 3,4,5,1,2,9 3 , 4 , 5 , 1 , 2 , 9
显然, ans(k)=4,ans(lson(k))=3,ans(rson(k))=2 a n s ( k ) = 4 , a n s ( l s o n ( k ) ) = 3 , a n s ( r s o n ( k ) ) = 2
但是,右儿子的答案对应的序列 1,2 1 , 2 是不会被计算在整个区间的答案里面的
时间复杂度: O(mlog2 n) O ( m log 2 ⁡   n )

代码

#include <bits/stdc++.h>
#define N 100010
using namespace std;
template <typename node> void chkmax(node &x, node y) {x = max(x, y);}
template <typename node> void chkmin(node &x, node y) {x = min(x, y);}
template <typename node> void read(node &x) {
    x = 0; int f = 1; char c = getchar();
    while (!isdigit(c)) {if (c == '-') f = -1; c = getchar();}
    while (isdigit(c)) x = x * 10 + c - '0', c = getchar(); x *= f;
}
struct SegmentTree {
    struct Node {
        int l, r, ans;
        double mx;
    } t[N * 4];
    void build(int k, int l, int r) {
        t[k] = (Node) {l, r, 0, 0};
        if (l == r) return;
        int mid = (l + r) >> 1;
        build(k << 1, l, mid);
        build(k << 1 | 1, mid + 1, r);
    }
    int calc(int k, double x) {
        if (t[k].l == t[k].r) return t[k].mx > x;
        if (t[k << 1].mx <= x) return calc(k << 1 | 1, x);
        return t[k].ans - t[k << 1].ans + calc(k << 1, x);
    }
    void modify(int k, int x, double v) {
        int l = t[k].l, r = t[k].r;
        if (l == r) {t[k].ans = 1, t[k].mx = v; return;}
        int mid = (l + r) >> 1;
        if (x <= mid) modify(k << 1, x, v);
            else modify(k << 1 | 1, x, v);
        t[k].mx = max(t[k << 1].mx, t[k << 1 | 1].mx);
        t[k].ans = t[k << 1].ans + calc(k << 1 | 1, t[k << 1].mx);
    }
} T;
int main() {
    int n, m; read(n), read(m);
    T.build(1, 1, n);
    while (m--) {
        int x, y; read(x), read(y);
        T.modify(1, x, (double)y / x);
        cout << T.t[1].ans << "\n";
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值