标记永久化

线段树与标记永久化

标记永久化是线段树的一个技巧,常用于无法(或难以)进行\(pushdown\)的较复杂的数据结构如主席树,树套树等。

如何做?对每个节点维护\(sum\)\(add\)

考虑修改,当询问与当前区间重合时,更新\(add+=val\),对所有经过的区间\(sum+=val\cdot (r-l+1)\)

void modify(int rt, int l, int r, int L, int R, int v)
{
    sum[rt]+=v*(R-L+1);
    if (l==L && r==R){add[rt]+=v; return;}
    if (R<=mid) modify(ls, l, mid, L, R, v);
    else if (L>mid) modify(rs, mid+1, r, L, R, v);
    else modify(ls, l, mid, L, mid, v),
        modify(rs, mid+1, r, mid+1, R, v);
}

考虑询问,累加经过的区间的\(add\),答案即为\(sum_{l,r}+\sum add\cdot (r-l+1)\)

int query(int rt, int l, int r, int L, int R, int Add)
{
    if (l==L && r==R) return sum[rt]+Add*(R-L+1);
    if (R<=mid) return query(ls, l, mid, L, R, Add+add[rt]);
    else if (L>mid) return query(rs, mid+1, r, L, R, Add+add[rt]);
    else return query(ls, l, mid, L, mid, Add+add[rt])
        +query(rs, mid+1, r, mid+1, R, Add+add[rt]);
}

一道模板

普通线段树区间加\(+\)区间求和,可以练练手。

#include<cstdio>
#define int long long
#define rep(i, a, b) for (register int i=(a); i<=(b); ++i)
#define per(i, a, b) for (register int i=(a); i>=(b); --i)
using namespace std;
const int N=1000005;
int sum[N], add[N], a[N];

inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}

#define mid (l+r>>1)
#define ls (rt<<1)
#define rs (rt<<1|1)

void build(int rt, int l, int r)
{
    if (l==r) {sum[rt]=a[l]; return;}
    build(ls, l, mid); build(rs, mid+1, r);
    sum[rt]=sum[ls]+sum[rs]; 
}

void modify(int rt, int l, int r, int L, int R, int v)
{
    sum[rt]+=v*(R-L+1);
    if (l==L && r==R){add[rt]+=v; return;}
    if (R<=mid) modify(ls, l, mid, L, R, v);
    else if (L>mid) modify(rs, mid+1, r, L, R, v);
    else modify(ls, l, mid, L, mid, v),
        modify(rs, mid+1, r, mid+1, R, v);
}

int query(int rt, int l, int r, int L, int R, int Add)
{
    if (l==L && r==R) return sum[rt]+Add*(R-L+1);
    if (R<=mid) return query(ls, l, mid, L, R, Add+add[rt]);
    else if (L>mid) return query(rs, mid+1, r, L, R, Add+add[rt]);
    else return query(ls, l, mid, L, mid, Add+add[rt])
        +query(rs, mid+1, r, mid+1, R, Add+add[rt]);
}

#undef mid
#undef ls
#undef rs

signed main()
{
    int n=read(), m=read();
    rep(i, 1, n) a[i]=read();
    build(1, 1, n);
    rep(i, 1, m)
    {
        int opt=read(), x=read(), y=read();
        if (opt==1) {int k=read(); modify(1, 1, n, x, y, k);}
        if (opt==2) printf("%lld\n", query(1, 1, n, x, y, 0));
    }
    return 0;
}

一道应用

也算是主席树上标记永久化的模板吧。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100005;
int rt[N], ls[N<<5], rs[N<<5], add[N<<5], tot, tim;
long long sum[N<<5];

inline int read()
{
    int x=0,f=1;char ch=getchar();
    for (;ch<'0'||ch>'9';ch=getchar()) if (ch=='-') f=-1;
    for (;ch>='0'&&ch<='9';ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*f;
}

int build(int rt, int l, int r)
{
    rt=++tot; if (l==r) {scanf("%lld", &sum[rt]); return rt;}
    int mid=(l+r)>>1;
    ls[rt]=build(rt, l, mid); rs[rt]=build(rt, mid+1, r);
    sum[rt]=sum[ls[rt]]+sum[rs[rt]];
    return rt;
}

int update(int oldrt, int L, int R, int l, int r, int x)
{
    int rt=++tot;
    ls[rt]=ls[oldrt]; rs[rt]=rs[oldrt];
    add[rt]=add[oldrt]; sum[rt]=sum[oldrt];
    sum[rt]+=1ll*(min(r, R)-max(l, L)+1)*x;
    if (l>=L && r<=R) {add[rt]+=x; return rt;}
    int mid=(l+r)>>1;
    if (L<=mid) ls[rt]=update(ls[rt], L, R, l, mid, x);
    if (R>mid) rs[rt]=update(rs[rt], L, R, mid+1, r, x);
    return rt;
}

long long query(int rt, int L, int R, int l, int r)
{
    if (l>=L && r<=R) return sum[rt];
    long long res=1ll*(min(r, R)-max(l, L)+1)*add[rt];
    int mid=(l+r)>>1;
    if (L<=mid) res+=query(ls[rt], L, R, l, mid);
    if (R>mid) res+=query(rs[rt], L, R, mid+1, r);
    return res;
}

void clear()
{
    tot=tim=0;
    memset(sum, 0, sizeof(sum));
    memset(rt, 0, sizeof(rt));
    memset(add, 0, sizeof(add));
}

int main()
{
    int n, m;
    while (~scanf("%d%d", &n, &m))
    {
        clear(); rt[0]=build(0, 1, n);
        while (m--)
        {
            char opt; scanf(" %c", &opt);
            if (opt=='C') 
            {
                int l=read(), r=read(), d=read();
                rt[++tim]=update(rt[tim-1], l, r, 1, n, d);
            }
            if (opt=='Q')
            {
                int l=read(), r=read();
                printf("%lld\n", query(rt[tim], l, r, 1, n));
            }
            if (opt=='H')
            {
                int l=read(), r=read(), t=read();
                printf("%lld\n", query(rt[t], l, r, 1, n));
            }
            if (opt=='B') tim=read();
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/ACMSN/p/10792961.html

### 线段树标记永久化在单点查询中的实现方法 线段树的标记永久化是一种优化技术,它通过共用节点的方式避免了传统线段树中频繁的标记下传操作。这种方法在线段树的每次更新和查询时直接将标记信息保存下来,无需每次都进行下推操作[^2]。 对于单点查询而言,标记永久化的实现逻辑区间查询类似,但更简单。以下是具体的实现方法: 1. **数据结构设计**: 需要维护两个数组: - `sum[]`:记录每个区间的值(例如区间和)。 - `add[]`:记录每个区间的懒惰标记(即需要增加的值)。 在单点查询中,`add[]`主要用于记录区间修改的影响。 2. **更新操作**: 当对某个区间进行修改时,如果当前区间完全包含修改区间,则仅更新该区间的`add[]`值;否则,递归更新子区间,并根据重叠部分调整`sum[]`值[^3]。 3. **查询操作**: 单点查询时,从根节点开始向下遍历,直到找到目标叶子节点。在遍历过程中,累加经过的所有节点的`add[]`值,最终结果为叶子节点的`sum[]`值加上所有经过路径上的`add[]`值之和。 以下是基于标记永久化的单点查询实现代码示例: ```python class SegmentTree: def __init__(self, n): self.n = n self.sum = [0] * (4 * n) # 存储区间和 self.add = [0] * (4 * n) # 存储懒惰标记 def pushup(self, rt): self.sum[rt] = self.sum[rt << 1] + self.sum[rt << 1 | 1] def update(self, L, R, C, l, r, rt): if L <= l and r <= R: # 完全包含 self.add[rt] += C self.sum[rt] += C * (r - l + 1) return mid = (l + r) // 2 if L <= mid: self.update(L, R, C, l, mid, rt << 1) if R > mid: self.update(L, R, C, mid + 1, r, rt << 1 | 1) self.pushup(rt) def query(self, idx, l, r, rt): if l == r: # 到达叶子节点 return self.sum[rt] mid = (l + r) // 2 res = self.add[rt] # 累加当前节点的懒惰标记 if idx <= mid: return res + self.query(idx, l, mid, rt << 1) else: return res + self.query(idx, mid + 1, r, rt << 1 | 1) # 示例使用 n = 10 st = SegmentTree(n) st.update(1, 5, 3, 1, n, 1) # 区间 [1, 5] 每个位置加 3 print(st.query(3, 1, n, 1)) # 查询位置 3 的值 ``` ### 实现细节说明 - **更新函数**:当修改区间完全包含当前区间时,直接更新`add[]`和`sum[]`;否则递归更新子区间。 - **查询函数**:从根节点开始向下遍历,直到找到目标叶子节点。在遍历过程中,累加经过的所有节点的`add[]`值。 - **时间复杂度**:单点查询的时间复杂度为 \(O(\log N)\),其中 \(N\) 是线段树的大小。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值