【清华集训 2013】楼房重建(线段树)

本文介绍了一种使用线段树动态维护单调栈长度的方法,通过单点修改支持整体查询,适用于处理如楼房重建等问题。算法核心在于维护区间最大值与单调栈长度,采用递归策略优化时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:【清华集训 2013】楼房重建

题意转化:动态维护一个单调栈,支持单点修改,整体查询单调栈长度。

使用线段树。对于每个节点,维护 max[root] m a x [ r o o t ] 表示区间最大值, cnt[root] c n t [ r o o t ] 表示区间中的单调栈的长度。在自底向上 maintain 的时候, cnt[root]=cnt[lson]+calc(rson,max[lson]) c n t [ r o o t ] = c n t [ l s o n ] + c a l c ( r s o n , m a x [ l s o n ] ) ,其中 calc(root,value) c a l c ( r o o t , v a l u e ) 表示只保留 >value > v a l u e 的元素, root r o o t 区间的单调栈长度。

我们考虑如何计算 calc c a l c 。当 value>max[lson] v a l u e > m a x [ l s o n ] 时,说明左区间对答案没有贡献,答案为 calc(rson,value) c a l c ( r s o n , v a l u e ) 。否则,答案为 calc(lson,value)+calc(rson,max[lson]) c a l c ( l s o n , v a l u e ) + c a l c ( r s o n , m a x [ l s o n ] )

我们发现 valuemax[lson] v a l u e ≤ m a x [ l s o n ] 的时候两边都要递归下去,时间复杂度时 Θ(n) Θ ( n ) 的,会超时。其实 calc(rson,max[lson]) c a l c ( r s o n , m a x [ l s o n ] ) 就是 cnt[root]cnt[lson] c n t [ r o o t ] − c n t [ l s o n ] (请读者自行思考)。这样,我们就得到了一个 Θ(mlog22n) Θ ( m log 2 2 ⁡ n ) 的算法。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

typedef pair<int, int> pii;
const int maxn = 100005;
const int maxm = 1 << 18 | 1;
int n, m, cnt[maxm];
double val[maxn], mx[maxm];

int calc(int u, int l, int r, double x) {
    if (l == r) {
        if (x < mx[u]) {
            return 1;
        } else {
            return 0;
        }
    }
    int mid = (l + r) >> 1;
    int ls = u << 1, rs = ls | 1;
    if (x < mx[ls]) {
        return cnt[u] - cnt[ls] + calc(ls, l, mid, x);
    } else {
        return calc(rs, mid + 1, r, x);
    }
}

void modify(int u, int l, int r, int x, double y, double z) {
    if (l == r) {
        mx[u] = y;
        cnt[u] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    int ls = u << 1, rs = ls | 1;
    if (x <= mid) {
        modify(ls, l, mid, x, y, z);
    } else {
        modify(rs, mid + 1, r, x, y, mx[ls]);
    }
    mx[u] = max(mx[ls], mx[rs]);
    cnt[u] = cnt[ls] + calc(rs, mid + 1, r, mx[ls]);
}

int main() {
    scanf("%d %d", &n, &m);
    for (int a, b, i = 1; i <= m; i++) {
        scanf("%d %d", &a, &b);
        val[a] = 1. * b / a;
        modify(1, 1, n, a, val[a], -1);
        printf("%d\n", cnt[1]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值