1731. 珂朵莉树

介绍两种解法。(分块理论上也能做,感兴趣自行实现)

珂朵莉树解法

纯模板,不解释。仅适用于随机数据,非随机数据或初始不是常数列会 TLE 。复杂度 O(n\log\log n)

#include <bits/stdc++.h> using namespace std; #define sc(x) scanf("%lld", &x) typedef long long ll; ll n; struct node_t { ll l, r; mutable ll v; // mutable使得const可变(set元素是const) node_t(const ll &il, const ll &ir, const ll &iv) : l(il), r(ir), v(iv) {} inline bool operator<(const node_t &o) const { return l < o.l; } }; set<node_t> odt; //建立一棵珂朵莉树 auto split(ll x) //珂朵莉基操 { if (x > n) return odt.end(); auto it = --odt.upper_bound(node_t{x, 0, 0}); if (it->l == x) return it; ll l = it->l, r = it->r, v = it->v; odt.erase(it); odt.insert(node_t(l, x - 1, v)); return odt.insert(node_t(x, r, v)).first; } void assign(ll l, ll r, ll v) //区间赋值为v, 均摊O(loglogn) { auto itr = split(r + 1), itl = split(l); odt.erase(itl, itr); odt.insert(node_t(l, r, v)); } void add(ll l, ll r, ll v) //区间增加v, 均摊O(loglogn) { auto itr = split(r + 1), itl = split(l); for (; itl != itr; ++itl) //枚举每个子区间 { itl->v = itl->v + v; } } ll query(ll l, ll r) //区间查询, 均摊O(loglogn) { ll res = 0; auto itr = split(r + 1), itl = split(l); for (; itl != itr; ++itl) { // itl->l, itl->r, itl->v 是当前子区间的左右端点和值 res += (itl->r - itl->l + 1) * (itl->v); } return res; } signed main() { sc(n); odt.insert({1, n, 1236895}); //初始化区间 ll m, cmd, l, r, x; for (sc(m); m--;) { sc(cmd), sc(l), sc(r); if (cmd == 1) { sc(x), assign(l, r, x); } else if (cmd == 2) { sc(x), add(l, r, x); } else { printf("%lld\n", query(l, r)); } } return 0; }

线段树解法

可以认为也是线段树比较经典的模板题。适用面远大于珂朵莉树。

有两个不同的区间修改操作,它们会相互影响,所以要开两个懒标记

laz1 代表操作 1 的懒标记(初始为 0 ,0 代表没有需要执行的懒操作),用 laz2 代表操作 2 的懒标记(初始也为 0)

对每次操作 1 ,区间赋值,把 laz1 设为 x ,把 laz2 设为 0 ,因为赋值后加法全无效了

对每次操作 2 ,区间加法,把 laz2 加上 x

根据这个想法,需要对每次 pushdown 操作,先判 laz1 ,如果发现 laz1 不为 0 ,就把 laz1 下传给两儿子,并且把两儿子的 laz2 清零 (注意:这很重要)。判完之后,再判并下传 laz2

如果没有 laz1 下传的时候把两儿子 laz2 清零,在区间赋值时,两儿子可能会多执行赋值前遗留的区间加法,从而造成结果偏大

复杂度 O((n+m)\log n)

#include <bits/stdc++.h> using namespace std; #define sc(x) scanf("%lld", &x) typedef long long ll; #define mn 100010 #define lfs p << 1 #define rfs p << 1 | 1 #define mkcf ll cf = (lf + rf) >> 1 // laz1: lazy-assign ; laz2: lazy-add ll t[mn << 2], laz1[mn << 2], laz2[mn << 2], a[mn]; void build(ll p, ll lf, ll rf) { if (lf == rf) { t[p] = 1236895; return; } mkcf; build(lfs, lf, cf); build(rfs, cf + 1, rf); t[p] = t[lfs] + t[rfs]; } void pushdown(ll p, ll lf, ll rf) { mkcf; if (laz1[p]) { t[lfs] = laz1[p] * (cf - lf + 1); t[rfs] = laz1[p] * (rf - cf); laz1[lfs] = laz1[rfs] = laz1[p]; laz2[lfs] = laz2[rfs] = 0; //注意要清理laz2 laz1[p] = 0; } t[lfs] += laz2[p] * (cf - lf + 1); t[rfs] += laz2[p] * (rf - cf); laz2[lfs] += laz2[p], laz2[rfs] += laz2[p]; laz2[p] = 0; } void assign(ll p, ll lf, ll rf, ll lc, ll rc, ll x) { if (lf >= lc && rf <= rc) { t[p] = (rf - lf + 1) * x; laz1[p] = x; laz2[p] = 0; return; } pushdown(p, lf, rf); mkcf; if (cf >= lc) { assign(lfs, lf, cf, lc, rc, x); } if (cf + 1 <= rc) { assign(rfs, cf + 1, rf, lc, rc, x); } t[p] = t[lfs] + t[rfs]; } void add(ll p, ll lf, ll rf, ll lc, ll rc, ll x) { if (lf >= lc && rf <= rc) { t[p] += (rf - lf + 1) * x; laz2[p] += x; return; } pushdown(p, lf, rf); mkcf; if (cf >= lc) { add(lfs, lf, cf, lc, rc, x); } if (cf + 1 <= rc) { add(rfs, cf + 1, rf, lc, rc, x); } t[p] = t[lfs] + t[rfs]; } ll query(ll p, ll lf, ll rf, ll lc, ll rc) { if (lf >= lc && rf <= rc) { return t[p]; } pushdown(p, lf, rf); mkcf; ll res = 0; if (cf >= lc) { res += query(lfs, lf, cf, lc, rc); } if (cf + 1 <= rc) { res += query(rfs, cf + 1, rf, lc, rc); } t[p] = t[lfs] + t[rfs]; return res; } signed main() { ll n, m, l, r, x, cmd; sc(n), sc(m); build(1, 1, n); while (m--) { sc(cmd), sc(l), sc(r); if (cmd == 1) { sc(x); assign(1, 1, n, l, r, x); } else if (cmd == 2) { sc(x); add(1, 1, n, l, r, x); } else if (cmd == 3) { printf("%lld\n", query(1, 1, n, l, r)); } } return 0; }