线段树标记永久化——可持久化前提

题目链接

以这道例题为例:
P3372 【模板】线段树 1

解题思路

我们当然用线段树来做这题。
不同于我们以前写的标记下传pushdown(),我们可以使用一种标记永久化的方法。
这种算法的核心是:
一个点的标记只增不减
拒绝使用pushup维护信息
这样的操作,在树套树、主席树中很有用武之地。

具体实现

我们如何完成这样的神奇操作?
关于一个点的标记只增不减,我们可以这样做

  • 不使用pushdown标记下传;对应地,在每次询问中计算每个答案区间应该有的标记值(即它到根节点的标记值之和)

关于拒绝使用pushup维护信息,我们可以这样做

  • 在修改操作中的递归操作中就完成对于子节点的贡献计算

详细代码

#define USEFASTERREAD 1

#define rg register
#define inl inline
#define DEBUG printf("qwq\n")
#define DEBUGd(x) printf("var %s is %lld", #x, ll(x))
#define DEBUGf(x) printf("var %s is %llf", #x, double(x))
#define putln putchar('\n')
#define putsp putchar(' ')
#define Rep(a, s, t) for(rg int a = s; a <= t; a++)
#define Repdown(a, t, s) for(rg int a = t; a >= s; a--)
typedef long long ll;
typedef unsigned long long ull;
#include<cstdio>

#if USEFASTERREAD
char In[1 << 20], *ss = In, *tt = In;
#define getchar() (ss == tt && (tt = (ss = In) + fread(In, 1, 1 << 20, stdin), ss == tt) ? EOF : *ss++)
#endif
namespace IO {
	inl void RS() {freopen("test.in", "r", stdin), freopen("test.out", "w", stdout);}
	inl ll read() {
		ll 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 * 10 + int(ch - '0');
		return x * f;
	}
	inl void write(ll x) {
		if(x < 0) {putchar('-'); x = -x;}
		if(x >= 10) write(x / 10);
		putchar(x % 10 + '0');
	}
	inl void writeln(ll x) {write(x), putln;}
	inl void writesp(ll x) {write(x), putsp;}
}
using namespace IO;
template<typename T> inline T Max(const T& x, const T& y) {return y < x ? x : y;}
template<typename T> inline T Min(const T& x, const T& y) {return y < x ? y : x;}
template<typename T> inline void Swap(T& x, T& y) {T tmp = x; x = y; y = tmp;}
template<typename T> inline T Abs(const T& x) {return x < 0 ? -x : x;}
const int MAXN = 100005;

struct Segment_tree {
    #define ls o << 1
    #define rs o << 1 | 1
    int l[MAXN << 2], r[MAXN << 2];
    ll v[MAXN << 2], la[MAXN << 2];
    void build(int o, int L, int R, ll a[]) {
        l[o] = L, r[o] = R;
        if(L == R) {
            v[o] = a[L];
            return;
        }
        int m = (L + R) >> 1;
        build(ls, L, m, a);
        build(rs, m + 1, R, a);
        v[o] = v[ls] + v[rs];
    }
    int len(int o, int x, int y) {
        return Min(y, r[o]) - Max(x, l[o]) + 1;
    }
    void add(int o, int x, int y, ll k) {
        v[o] += k * len(o, x, y);
        if(x <= l[o] && r[o] <= y) {
            la[o] += k;
            return;
        }
        int m = (l[o] + r[o]) >> 1;
        if(x <= m) add(ls, x, y, k);
        if(y > m) add(rs, x, y, k);
    }
    ll ask(int o, int x, int y, ll tg) {
        if(x <= l[o] && r[o] <= y) return v[o] + tg * len(o, x, y);
        int m = (l[o] + r[o]) >> 1;
        ll ans = 0;
        if(x <= m) ans += ask(ls, x, y, tg + la[o]);
        if(y > m) ans += ask(rs, x, y, tg + la[o]);
        return ans;
    }
    #undef ls
    #undef rs
}tr;
int N, M;
ll a[MAXN];

int main() {
	//RS();
    N = read(), M = read();
    Rep(i, 1, N) a[i] = read();
    tr.build(1, 1, N, a);
    while(M--) {
        int opt = read(), l = read(), r = read();
        if (opt == 1) {
            ll k = read();
            tr.add(1, l, r, k);
        } else {
            writeln(tr.ask(1, l, r, 0));
        }
    }

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

日居月诸Rijuyuezhu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值