【日志】特定条件下线段树暴力单点修改的小优化

本文介绍了如何使用线段树优化处理不具有区间性质的操作,如区间取模。通过结合区间最大值的维护,实现了在特定条件下对单点修改的优雅优化。代码示例展示了如何在区间求和、区间取模和单点修改中应用这一技巧,从而避免暴力修改导致的时间复杂度过高。
摘要由CSDN通过智能技术生成

特定条件下线段树暴力单点修改的小优化

有的时候一些区间修改条件不符合区间的性质(比如加法在区间上可以以和为区间性质,但是众数不行)。但是某些特定的条件下可以借助其他的具有区间的性质的条件来优化暴力单点修改。

CF438D The Child and Sequence

传送门

题意:实现区间取模、区间求和,单点修改。

区间求和和单点修改很简单,但是问题在于区间取模。区间取模是一个不具有区间性质的条件。

显然,对一堆数取模再累加的结果和对其和取模的结果一定是不同的。像这种操作显然只能暴力修改(当然也可以用优雅的暴力,也就是分块做,或者其他方式,这里说的是线段树)。当然,暴力单点修改的结果一定是喜得TLE。

虽然区间取模不满足能够直接对区间进行操作,但是取模操作却有一个很明显的特征:只有对大于模数的数才需要进行取模。如果一段区间内的数字都小于这个模数,显然是不用再去暴力修改的。如果能有一个标记,表示这个区间存在着一个大于模数的数,那么意味着这个区间需要被修改。

这个特别的标记恰好和维护区间最大值相对应了。如果这个区间的最大值大于模数,这段区间才需要进行操作,否则不需要。同理,大区间再递归分成小区间时,再判断。这样一来,原本十分暴力的单点修改就变成了十分优雅的暴力单点修改了。

大致代码如下。

#inlcude <bits/stdc++.h>
using namespace std;
using ll = long long;

struct Node {  //线段树结点定义
  	ll sum, maxn;
    int lc, rc;
};
const int MAXN = 1e5 + 10;
Node tr[MAXN << 4];
int tot = 1;  // 堆式存储也可以
ll a[MAXN];

// 看个人怎么喜欢命名吧,有的人喜欢用pushup
// 主要是自己打update的速度比pushup更快
// 而且对我来说这个是对当前结点的更新
inline void update(int now) {
    tr[now].sum = tr[tr[now].lc].sum + tr[tr[now].rc].sum;
    tr[now].maxn = max(tr[tr[now].lc].maxn. tr[tr[now].rc].maxn);
}

void build(int l, int r, int now) {
    if (l == r) {
        tr[now].sum = tr[now].maxn = a[l];
        return;
    }
    int mid = l + ((r - l) >> 1);
    tr[now].lc = ++ tot;
    build(l, mid, tr[now].lc);
    tr[now].rc = ++ tot;
    build(mid + 1, r, tr[now].rc);
    update(now);
}

ll query(int sl, int sr, int ll, int rr, int now) {
    if (sl <= ll && rr <= sr)
        return tr[now].sum;
	ll res = 0;
    int mid = ll + ((rr - ll) >> 1);
    if (sl <= mid)
    	res += query(sl, sr, ll, mid, tr[now].lc);
    if (sr > mid)
    	res += query(sl, sr, mid + 1, rr, tr[now].rc);
    return res;
}

void change(int x, ll val, int ll, int rr, int now) {
    if (ll == rr) {
        tr[now].sum = tr[now].maxn = val;
        return;
    }
    int mid = ll + ((rr - ll) >> 1);
    if (x <= mid)
        change(x, val, ll, mid, tr[now].lc);
    else
        change(x, val, mid + 1, rr, tr[now].rc);
    update(now);
}

// 有点像是单点修改与区间修改的混合,当然这种写法不一定(也应该)不是最好的
void mod(int sl, int sr, ll p, int ll, int rr, int now) {
    if (ll == rr) {
        if (tr[now].sum >= p) {
            tr[now].maxn = (tr[now].sum %= p);
        }
        return;
    }
    if (sl <= ll && rr <= sr) {
        if (tr[now].maxn >= p) {
            int mid = ll + ((rr - ll) >> 1);
            if (tr[tr[now].lc].maxn >= p)
                mod(sl, sr, p, ll, mid, tr[now].lc);
            if (tr[tr[now].rc].maxn >= p)
                mod(sl, sr, p, mid + 1, rr, tr[now].rc);
        	update(now);
        }
        return;
    }
    int mid = ll + ((rr - ll) >> 1);
    if (sl <= mid)
        mod(sl, sr, p, ll, mid, tr[now].lc);
    if (sr > mid)
        mod(sl, sr, p, mid + 1, rr, tr[now].rc);
    update(now);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值