2 条题解

  • 3
    @ 2026-8-12 21:31:19

    很好小朋友们,我们被要求写一个可持久化线段树,支持区间修改、区间复制和输出区间和。

    懒标记是必须的,但可持久化线段树一般不太支持 pushdown,尤其在本题中。

    在进入子节点之前,我们需要克隆当前节点,因为当前节点可能被其他版本共享。

    然后才能将其懒标记下推到新克隆出的子节点,最后将当前节点的懒标记重置。

    这样会导致不必要的新节点开销。

    于是我们不考虑 pushdown,而是在递归函数中同时传进祖先变化,即懒标记。

    线段树维护五个参数,分别是左右子节点、区间总和 bcb、c 懒标记。

    (1)处理操作 0

    我们构建一个 change 函数,对版本 p 的区间 [l,r][l, r] 施加变换 x>bx+cx -> b*x + c,返回新版本的根节点。

    可持久化的精髓是“改了才新建”,编写时请注意祖先变化的传递。

    (2)处理操作 1

    一个个复制绝对是不明智的,我们可以先构建一个 crossover 函数。

    合并两个版本 xxyy,返回一个新版本,其前 atat 个元素来自 xx,后 lenatlen - at 个元素来自 y。

    这样题目的 [l,r][l, r] 可以变成先处理 [1,r][1, r] 的复制,接着把原来的 [1,l1][1, l - 1] 复制回去。

    (3)处理操作 2

    正常的 query 函数即可。

    另注意:本题的边界和 base 十分复杂,我用的是统一 base-1。

    #include <bits/stdc++.h>
    using namespace std;
     
    typedef long long LL;
    const LL P = 998244353;          // 模数
    const int N = 1e5 + 10;          // 数组最大长度
     
    #define lc(p) tr[p].lc           // 左孩子下标
    #define rc(p) tr[p].rc           // 右孩子下标
    #define MID ((L + R) >> 1)       // 当前区间中点
     
    /*
      线段树节点结构:
        lc, rc    : 左右孩子指针(下标)
        sum       : 当前节点代表的区间和(已应用该节点的懒标记)
        lazy_b, lazy_c : 懒标记,表示对子区间施加的仿射变换 x -> lazy_b * x + lazy_c
    */
    struct node {
        int lc, rc;
        LL sum;
        LL lazy_b, lazy_c;
    };
     
    vector<node> tr;                 // 动态存储所有节点,0 号节点为空节点
    LL a[N];                         // 原始数组(1‑based)
     
    // 新建一个节点,懒标记初始为恒等变换 (1, 0)
    int newd() {
        tr.push_back({0, 0, 0, 1, 0});
        return (int)tr.size() - 1;
    }
     
    // 克隆节点 p,复制其所有信息
    int clone_node(int p) {
        tr.push_back(tr[p]);
        return (int)tr.size() - 1;
    }
     
    /*
      将仿射变换 x -> b*x + c 应用到节点 p 所代表的整个区间(长度为 len)
      同时更新该节点的懒标记,使其与原有懒标记复合。
      注意:应用顺序为 新变换 作用于 旧变换,即最终变换为 b*(old(x)) + c。
    */
    void modify(int p, LL b, LL c, LL len) {
        if (p == 0) return;
        tr[p].sum = (b * tr[p].sum + c * len) % P;
        tr[p].lazy_b = b * tr[p].lazy_b % P;
        tr[p].lazy_c = (b * tr[p].lazy_c % P + c) % P;
    }
     
    // 复合变换:返回 g(f()),即先 f 后 g
    pair<LL, LL> compose(LL g_b, LL g_c, LL f_b, LL f_c) {
        return { (g_b * f_b) % P, (g_b * f_c + g_c) % P };
    }
     
    // 用左右子树的 sum 更新当前节点的 sum
    void pushup(int p) {
        tr[p].sum = (tr[lc(p)].sum + tr[rc(p)].sum) % P;
    }
     
    // 建树,p 为引用返回根节点下标
    void build(int &p, int L, int R) {
        p = newd();
        if (L == R) {
            tr[p].sum = a[L] % P;
            return;
        }
        build(lc(p), L, MID);
        build(rc(p), MID + 1, R);
        pushup(p);
    }
     
    /*
      对版本 p 的区间 [l, r] 施加变换 x -> b*x + c,返回新版本的根节点。
      参数 prop_b, prop_c 表示从根到当前节点路径上所有祖先懒标记复合后的变换,
      需要在访问当前区间时应用。
      本函数采用路径复制,只修改必要的节点,保留未修改部分。
    */
    int change(int p, int L, int R, int l, int r, LL b, LL c, LL prop_b, LL prop_c) {
        // 完全不相交:克隆当前节点并应用祖先变换,然后返回
        if (r < L || R < l) {
            int np = clone_node(p);
            modify(np, prop_b, prop_c, R - L + 1);
            return np;
        }
        // 完全覆盖:克隆当前节点,整体施加 “祖先变换 + 当前变换”
        if (l <= L && R <= r) {
            auto t = compose(b, c, prop_b, prop_c); // 先 prop,后 (b,c)
            int np = clone_node(p);
            modify(np, t.first, t.second, R - L + 1);
            return np;
        }
     
        // 部分重叠:创建新节点,懒标记为恒等
        int np = newd();
        // 传递给子节点的祖先变换 = prop ∘ p.lazy(先 p.lazy,后 prop)
        auto t = compose(prop_b, prop_c, tr[p].lazy_b, tr[p].lazy_c);
     
        // 左子区间有重叠,递归处理
        if (l <= MID) {
            lc(np) = change(lc(p), L, MID, l, r, b, c, t.first, t.second);
        } 
        else {
            // 左子区间没有重叠,克隆并应用祖先变换
            if (lc(p) != 0) {
                int lc = clone_node(lc(p));
                modify(lc, t.first, t.second, MID - L + 1);
                lc(np) = lc;
            }
        }
     
        // 右子区间有重叠,递归处理
        if (r >= MID + 1) {
            rc(np) = change(rc(p), MID + 1, R, l, r, b, c, t.first, t.second);
        } 
        else {
            if (rc(p) != 0) {
                int rc = clone_node(rc(p));
                modify(rc, t.first, t.second, R - (MID + 1) + 1);
                rc(np) = rc;
            }
        }
     
        pushup(np);
        return np;
    }
     
    // 封装 update - change,初始祖先变换为恒等
    int update(int p, int L, int R, int l, int r, LL b, LL c) {
        return change(p, L, R, l, r, b, c, 1, 0);
    }
     
    /*
      合并两个版本 x 和 y,返回一个新版本,其前 at 个元素来自 x,后 len - at 个元素来自 y。
      参数 xb,xc 和 yb,yc 分别为两个版本当前需要应用的祖先变换。
      具体规则:
        - at <= 0         :全部取 y
        - at >= len       :全部取 x
        - 否则根据 at 与左子树长度的关系递归处理左右子树。
    */
    int crossover(int x, int y, int len, int at, LL xb, LL xc, LL yb, LL yc) {
        if (at <= 0) {                     // 全部来自 y
            if (y == 0) return 0;
            int ny = clone_node(y);
            modify(ny, yb, yc, len);
            return ny;
        }
        if (len <= at) {                   // 全部来自 x
            if (x == 0) return 0;
            int nx = clone_node(x);
            modify(nx, xb, xc, len);
            return nx;
        }
     
        int midl = (len + 1) >> 1;         // 左子树长度(尽量平衡)
        // 为啥是上取整?len = R - L + 1
        // MID = (R + L) / 2
        // midl = MID - L + 1
        // 2 * midl = R + L - 2 * L + 2 * 1
        // midl = R - L + 1
        LL nx_b, nx_c, ny_b, ny_c;
     
        // 将 x 的祖先变换与其自身懒标记复合,得到传递给左/右子树的变换
        if (x != 0) {
            auto res = compose(xb, xc, tr[x].lazy_b, tr[x].lazy_c);
            nx_b = res.first; nx_c = res.second;
        } 
        else {
            nx_b = xb; nx_c = xc;
        }
        if (y != 0) {
            auto res = compose(yb, yc, tr[y].lazy_b, tr[y].lazy_c);
            ny_b = res.first; ny_c = res.second;
        } 
        else {
            ny_b = yb; ny_c = yc;
        }
     
        int left_x = (x == 0) ? 0 : lc(x);
        int left_y = (y == 0) ? 0 : lc(y);
        int right_x = (x == 0) ? 0 : rc(x);
        int right_y = (y == 0) ? 0 : rc(y);
     
        // 左子树需要的前 at 个元素来自 x
        int lc = crossover(left_x, left_y, midl, at,
                           nx_b, nx_c, ny_b, ny_c);
        // 右子树需要的前 at - midl 个元素来自 x
        int rc = crossover(right_x, right_y, len - midl, at - midl,
                           nx_b, nx_c, ny_b, ny_c);
     
        int res = newd();
        lc(res) = lc;
        rc(res) = rc;
        pushup(res);
        return res;
    }
     
    /*
      操作 1 的封装:将版本 y 的区间 [l, r] 复制到版本 x 的对应位置。
      通过两次交叉实现:
        1. tmp = x[0 : l - 1] + y[l : n]
        2. 结果 = tmp[0 : r] + x[r : n]  = x[0 : l - 1] + y[l : r] + x[r : n]
    */
    int update_crossover_twice(int x, int y, int l, int r, int total_len) {
        int tmp = crossover(x, y, total_len, l - 1, 1, 0, 1, 0);
        return crossover(tmp, x, total_len, r, 1, 0, 1, 0);
    }
     
    /*
      查询版本 p 中区间 [l, r] 的和。
      参数 b, c 表示当前路径上所有祖先懒标记复合后的变换,
      在完全覆盖时直接应用到当前节点的 sum 上。
    */
    LL query(int p, int L, int R, int l, int r, LL b, LL c) {
        if (r < L || R < l) {
            return 0;
        }
        if (l <= L && R <= r) {
            return (b * tr[p].sum + c * (R - L + 1)) % P;
        }
     
        // 传递给子节点的祖先变换 = (b,c) ∘ p.lazy(先 p.lazy,后当前祖先)
        auto t = compose(b, c, tr[p].lazy_b, tr[p].lazy_c);
        LL res = 0;
        res += query(lc(p), L, MID, l, r, t.first, t.second);
        res += query(rc(p), MID + 1, R, l, r, t.first, t.second);
        return res % P;
    }
     
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(0);
     
        int n, Q;
        cin >> n >> Q;
        for (int i = 1; i <= n; i ++) cin >> a[i];
     
        tr.reserve(20000000);   // 预留节点空间
        tr.push_back({0, 0, 0, 1, 0});   // 0 号节点作为空节点
     
        int rt;
        build(rt, 1, n);
        vector<int> roots(Q + 1);        // roots[i] 存储版本 A_i 的根节点
        roots[0] = rt;                   // 初始版本 A_{-1}
     
        for (int i = 1; i <= Q; i++) {
            int opt;
            cin >> opt;
     
            // 输入中 k, s 为 0‑based 版本编号,-1 表示初始版本,所以用 k+1 索引 roots
            if (opt == 0) {              // 区间仿射变换
                int k, l, r;
                LL b, c;
                cin >> k >> l >> r >> b >> c;
                l ++;                     // 输入区间为 [l, r) 的 0‑based 下标,转为 1‑based
                int src = roots[k + 1];
                roots[i] = update(src, 1, n, l, r, b, c);
            } 
            else if (opt == 1) {       // 区间从另一版本复制
                int k, s, l, r;
                cin >> k >> s >> l >> r;
                l ++;
                int src_k = roots[k + 1];
                int src_s = roots[s + 1];
                roots[i] = update_crossover_twice(src_k, src_s, l, r, n);
            } 
            else {                     // 区间求和查询
                int k, l, r;
                cin >> k >> l >> r;
                l ++;
                int src = roots[k + 1];
                cout << query(src, 1, n, l, r, 1, 0) << "\n";
                roots[i] = roots[i - 1];
            }
        }
        return 0;
    }
    
    
    
    • -2
      @ 2026-8-15 10:31:54

      哪个小馋猫这么爱吃

      #include <cstdio>
      #include <string>
      #include <vector>
      #include <algorithm>
      #include <array>
      
      #include <utility>
      
      #include <cassert>
      namespace nachia{
      
      // ax + by = gcd(a,b)
      // return ( x, - )
      std::pair<long long, long long> ExtGcd(long long a, long long b){
          long long x = 1, y = 0;
          while(b){
              long long u = a / b;
              std::swap(a-=b*u, b);
              std::swap(x-=y*u, y);
          }
          return std::make_pair(x, a);
      }
      
      } // namespace nachia
      
      namespace nachia{
      
      template<unsigned int MOD>
      struct StaticModint{
      private:
          using u64 = unsigned long long;
          unsigned int x;
      public:
      
          using my_type = StaticModint;
          template< class Elem >
          static Elem safe_mod(Elem x){
              if(x < 0){
                  if(0 <= x+MOD) return x + MOD;
                  return MOD - ((-(x+MOD)-1) % MOD + 1);
              }
              return x % MOD;
          }
      
          StaticModint() : x(0){}
          StaticModint(const my_type& a) : x(a.x){}
          StaticModint& operator=(const my_type&) = default;
          template< class Elem >
          StaticModint(Elem v) : x(safe_mod(v)){}
          unsigned int operator*() const noexcept { return x; }
          my_type& operator+=(const my_type& r) noexcept { auto t = x + r.x; if(t >= MOD) t -= MOD; x = t; return *this; }
          my_type operator+(const my_type& r) const noexcept { my_type res = *this; return res += r; }
          my_type& operator-=(const my_type& r) noexcept { auto t = x + MOD - r.x; if(t >= MOD) t -= MOD; x = t; return *this; }
          my_type operator-(const my_type& r) const noexcept { my_type res = *this; return res -= r; }
          my_type operator-() const noexcept { my_type res = *this; res.x = ((res.x == 0) ? 0 : (MOD - res.x)); return res; }
          my_type& operator*=(const my_type& r)noexcept { x = (u64)x * r.x % MOD; return *this; }
          my_type operator*(const my_type& r) const noexcept { my_type res = *this; return res *= r; }
          my_type pow(unsigned long long i) const noexcept {
              my_type a = *this, res = 1;
              while(i){ if(i & 1){ res *= a; } a *= a; i >>= 1; }
              return res;
          }
          my_type inv() const { return my_type(ExtGcd(x, MOD).first); }
          unsigned int val() const noexcept { return x; }
          static constexpr unsigned int mod() { return MOD; }
          static my_type raw(unsigned int val) noexcept { auto res = my_type(); res.x = val; return res; }
          my_type& operator/=(const my_type& r){ return operator*=(r.inv()); }
          my_type operator/(const my_type& r) const { return operator*(r.inv()); }
      };
      
      } // namespace nachia
      
      namespace nachia {
      
      template<
          class S,
          class F,
          S op(S l, S r),
          F composition(F f, F x),
          S mapping(F f, S x)
      >
      struct PersistentLazySegtree {
      public:
          struct Node {
              int l;
              int r;
              S a;
              F f;
          };
          struct Agent{
          public:
              Agent(){}
              Agent(int _sz, int _root, PersistentLazySegtree* _q) : sz(_sz), root(_root), q(_q) {}
              int size() const { return sz; }
              Agent set(int at, S x) const { return copy(q->set(root,sz,at,x,q->id)); }
              S prod(int l, int r) const { return q->prod(root,sz,l,r,q->id); }
              Agent apply(int l, int r, F f) const { return copy(q->apply(root,sz,l,r,f,q->id)); }
              Agent crossover(Agent right, int p) const {
                  return copy(q->crossover(root, right.root, sz, p, q->id, q->id));
              }
          private:
              int sz;
              int root;
              PersistentLazySegtree* q = nullptr;
              Agent copy(int newRoot) const { return Agent(sz, newRoot, q); }
          };
      
          PersistentLazySegtree(){}
      
          PersistentLazySegtree(S _e, F _id, int reserved_size = 0)
              : e(_e), id(_id) { v.reserve(reserved_size); }
      
          Agent construct(const std::vector<S>& val){
              return { int(val.size()), fromRange(val.begin(), int(val.size())), this };
          }
      
      private:
          int newLeaf(S x){
              int res = v.size();
              v.push_back({ -1, -1, x, id });
              return res;
          }
          int newMid(int l, int r){
              int res = v.size();
              v.push_back({ l, r, op(v[l].a, v[r].a), id });
              return res;
          }
          int applyAtNode(int p, F f){
              if(v[p].l == -1) return newLeaf(mapping(f, v[p].a));
              int res = v.size();
              v.push_back({ v[p].l, v[p].r, mapping(f, v[p].a), composition(f, v[p].f) });
              return res;
          }
          int set(int p, int n, int at, S x, F prop){
              if(n == 1) return newLeaf(x);
              int m = n / 2;
              auto nxf = composition(prop, v[p].f);
              if(at < m) return newMid(set(v[p].l, m, at, x, nxf), applyAtNode(v[p].r, nxf));
              return newMid(applyAtNode(v[p].l, nxf), set(v[p].r, n-m, at-m, x, nxf));
          }
          S prod(int p, int n, int l, int r, F prop){
              if(l <= 0 && n <= r) return mapping(prop, v[p].a);
              if(r <= 0 || n <= l) return e;
              int m = n / 2;
              auto nxf = composition(prop, v[p].f);
              return op(
                  prod(v[p].l, m, l, r, nxf),
                  prod(v[p].r, n-m, l-m, r-m, nxf)
              );
          }
          int apply(int p, int n, int l, int r, F f, F prop){
              if(l <= 0 && n <= r) return applyAtNode(p, composition(f, prop));
              if(r <= 0 || n <= l) return applyAtNode(p, prop);
              int m = n / 2;
              auto nxf = composition(prop, v[p].f);
              int l2 = apply(v[p].l, m, l, r, f, nxf);
              int r2 = apply(v[p].r, n-m, l-m, r-m, f, nxf);
              return newMid(l2, r2);
          }
          int crossover(int p, int q, int n, int at, F propl, F propr){
              if(at <= 0) return applyAtNode(q, propr);
              if(n <= at) return applyAtNode(p, propl);
              int m = n / 2;
              auto nxfl = composition(propl, v[p].f);
              auto nxfr = composition(propr, v[q].f);
              int l2 = crossover(v[p].l, v[q].l, m, at, nxfl, nxfr);
              int r2 = crossover(v[p].r, v[q].r, n-m, at-m, nxfl, nxfr);
              return newMid(l2, r2);
          }
          int fromRange(typename std::vector<S>::const_iterator a, int n){
              if(n == 1) return newLeaf(a[0]);
              int m = n / 2;
              return newMid(fromRange(a, m), fromRange(a+m, n-m));
          }
      
          S e;
          F id;
          std::vector<Node> v;
      };
      
      } // namespace nachia
      
      template<class T, int sz>
      struct ValArrayOverRing {
          using X = ValArrayOverRing;
          std::array<T, sz> m;
          T& operator[](int i){ return m[i]; }
          const T& operator[](int i) const { return m[i]; }
          X& operator+=(const X& r){ for(int i=0; i<sz; i++){ m[i] += r[i]; } return *this; }
          X& operator-=(const X& r){ for(int i=0; i<sz; i++){ m[i] -= r[i]; } return *this; }
          X& operator*=(const X& r){ for(int i=0; i<sz; i++){ m[i] *= r[i]; } return *this; }
          X operator+(const X& r) const { X p = *this; p += r; return p; }
          X operator-(const X& r) const { X p = *this; p -= r; return p; }
          X operator*(const X& r) const { X p = *this; p *= r; return p; }
      };
      
      namespace nachia {
      
      template<class Value>
      struct Affine{
          using X = ValArrayOverRing<Value, 2>;
          Value a;
          Value b;
          static Affine Id(){ return { Value(1), Value(0) }; }
          Affine operator()(const Affine& x) const { return { a * x.a, a * x.b + b }; }
          X operator()(const X& x) const { return { x[0], a * x[1] + b * x[0] }; }
          Affine operator+(const Affine& r) const { return { a + r.a, b + r.b }; }
          Affine operator-(const Affine& r) const { return { a - r.a, b - r.b }; }
          Affine& operator+=(const Affine& r) const { a += r.a; b += r.b; return *this; }
          Affine& operator-=(const Affine& r) const { a -= r.a; b -= r.b; return *this; }
      };
      
      } // namespace nachia
      
      
      using namespace std;
      
      using Modint = nachia::StaticModint<998244353>;
      using Affine = nachia::Affine<Modint>;
      using Value = Affine::X;
      Value op(Value l, Value r){ return l + r; }
      Value mapping(Affine f, Value x){ return f(x); }
      Affine composition(Affine f, Affine x){ return f(x); }
      
      vector<int> solve(int N, int Q, const vector<int>& A, const vector<array<int,6>>& queries){
          using Ds = nachia::PersistentLazySegtree<Value, Affine, op, composition, mapping>;
          Ds ds(Value(), Affine::Id());
          vector<Ds::Agent> data(Q+1);
          vector<Value> mA(N);
          for(int i=0; i<N; i++) mA[i] = { 1, A[i] };
          data[0] = ds.construct(mA);
          vector<int> ans;
          for(int qi=1; qi<=Q; qi++){
              auto& q = queries[qi-1];
              if(q[0] == 0){
                  auto [dum0, t, l, r, c, d] = q; t++;
                  Modint cm = c;
                  Modint dm = d;
                  data[qi] = data[t].apply(l, r, {cm,dm});
              }
              else if(q[0] == 1){
                  auto [dum0, t, s, l, r, dum1] = q; t++; s++;
                  data[qi] = data[t].crossover(data[s], l).crossover(data[t], r);
              }
              else if(q[0] == 2){
                  auto [dum0, t, l, r, dum1, dum2] = q; t++;
                  Modint v = data[t].prod(l, r)[1];
                  ans.push_back(int(v.val()));
              }
          }
          return ans;
      }
      
      
      int main(){
          int N, Q; scanf("%d%d", &N, &Q);
          vector<int> A(N);
          for(auto& a : A) scanf("%d", &a);
          vector<array<int, 6>> queries(Q);
          for(auto& q : queries){
              scanf("%d", &q[0]);
              if(q[0] == 0){
                  scanf("%d%d%d%d%d", &q[1], &q[2], &q[3], &q[4], &q[5]);
              } else if(q[0] == 1){
                  scanf("%d%d%d%d", &q[1], &q[2], &q[3], &q[4]);
                  q[5] = 0;
              } else if(q[0] == 2){
                  scanf("%d%d%d", &q[1], &q[2], &q[3]);
                  q[4] = q[5] = 0;
              } else exit(1);
          }
          auto ans = solve(N, Q, A, queries);
          for(auto a : ans) printf("%d\n", a);
          return 0;
      }
      
      
      • 1

      可持久化区间仿射区间和(Persistent Range Affine Range Sum)

      信息

      ID
      8131
      时间
      1000ms
      内存
      1024MiB
      难度
      9
      标签
      递交数
      45
      已通过
      2
      上传者