H. Add and Mod

对于一个区间,维护三个信息:

  • \(\text{sum}\):区间内所有正数的总和。
  • \(\text{suf}\):最后一个 \(A_i=0\) 之后的正数之和;如果区间内没有零,则 \(\text{suf} = \text{sum}\)。
  • \(\text{zero}\):区间内是否存在零。

设模数 \(M=998244353\)。
对于查询区间 \([l,r]\),设其聚合信息为 \(s\)。

情况一:区间内没有零
所有操作都是加法,答案为

\[x+\text{s.sum} \]

情况二:区间内存在零
最后一个零之后的元素只会增加当前值,不会再取模。而最后一个零及其之前的操作等价于将初始 \(x\) 加上这些正数的总和后取模。因此答案为

\[(x+\text{s.sum}-\text{s.suf})\bmod M +\text{s.suf}. \]

合并左右区间时,如果右区间存在零,那么合并后的 \(\text{suf}\) 就是右区间的 \(\text{suf}\);否则,合并后的 \(\text{suf}\) 等于左区间的 \(\text{suf}\) 加上右区间的 \(\text{sum}\)。

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

const int mod = 998244353;

struct S {
    ll sum, suf;
    bool zero;
};
S op(S a, S b) {
    S c;
    c.sum = a.sum + b.sum;
    c.zero = a.zero or b.zero;

    if (b.zero) c.suf = b.suf;
    else c.suf = a.suf + b.sum;

    return c;
}
S e() {
    return {0, 0, false};
}

S toS(int x) {
    if (x == 0) return {0, 0, true};
    return {x, x, false};
}

int main() {
    int n, q;
    cin >> n >> q;
    
    segtree<S, op, e> seg(n);
    rep(i, n) {
        int a;
        cin >> a;
        seg.set(i, toS(a));
    }
    
    rep(qi, q) {
        int type;
        cin >> type;
        if (type == 1) {
            int i, x;
            cin >> i >> x;
            --i;
            seg.set(i, toS(x));
        }
        else {
            int l, r, x;
            cin >> l >> r >> x;
            --l;
            
            S s = seg.prod(l, r);
            ll ans;
            if (!s.zero) ans = x+s.sum;
            else ans = (x+s.sum-s.suf)%mod + s.suf;
            cout << ans << '\n';
        }
    }
    
    return 0;
}

I. Subarray

可以用线段树维护一个 \(5\) 阶最大加法矩阵。矩阵乘法满足结合律,因此可以直接作为线段树的合并操作。

矩阵的元素 \(D[i][j]\) 表示:处理完对应区间后,从状态 \(i\) 转移到状态 \(j\) 所能获得的最大元素和。
一共有 \(5\) 个状态,分别表示还没选择区间、正在选择第一个区间、第一个区间结束、正在选择第二个区间、第二个区间结束。矩阵只允许状态编号增大,因此合并两个区间时可以只计算上三角部分:

\[C_{i,j}=\max_{i\le k\le j}(A_{i,k}+B_{k,j}). \]

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

const ll INF = 1e18;

using A = array<ll, 5>;
using S = array<A, 5>;
S op(S a, S b) {
    S c;
    for (auto& r : c) r.fill(-INF);
    rep(i, 5) {
        for (int j = i; j < 5; ++j) {
            for (int k = i; k <= j; ++k) {
                c[i][j] = max(c[i][j], a[i][k]+b[k][j]);
            }
        }
    }
    return c;
}
S e() {
    S d;
    for (auto& r : d) r.fill(-INF);
    rep(i, 5) d[i][i] = 0;
    return d;
}

S toS(int x) {
    S d;
    for (auto& r : d) r.fill(-INF);
    
    d[0][0] = 0;
    d[0][1] = x;
    d[0][2] = x;
    
    d[1][1] = x;
    d[1][2] = x;
    
    d[2][2] = 0;
    d[2][3] = x;
    d[2][4] = x;
    
    d[3][3] = x;
    d[3][4] = x;
    
    d[4][4] = 0;

    return d;
}

int main() {
    int n, q;
    cin >> n >> q;
    
    segtree<S, op, e> seg(n);
    rep(i, n) {
        int a;
        cin >> a;
        seg.set(i, toS(a));
    }
    
    rep(qi, q) {
        int type;
        cin >> type;
        if (type == 1) {
            int i, x;
            cin >> i >> x;
            --i;
            seg.set(i, toS(x));
        }
        else {
            int l, r;
            cin >> l >> r;
            --l;
            
            S d = seg.prod(l, r);
            ll ans = 0;
            rep(i, 5) ans = max(ans, d[0][i]);
            cout << ans << '\n';
        }
    }
    
    return 0;
}

J. Long Binary String

可以将所有询问的左右端点离散化,并使用线段树维护区间内 \(1\) 的数量。

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

// Coodinate Compression
template<typename T=int>
struct CC {
  bool initialized;
  vector<T> xs;
  CC(): initialized(false) {}
  void add(T x) { xs.push_back(x);}
  void init() {
    sort(xs.begin(), xs.end());
    xs.erase(unique(xs.begin(),xs.end()),xs.end());
    initialized = true;
  }
  int operator()(T x) {
    if (!initialized) init();
    return upper_bound(xs.begin(), xs.end(), x) - xs.begin() - 1;
  }
  T operator[](int i) {
    if (!initialized) init();
    return xs[i];
  }
  int size() {
    if (!initialized) init();
    return xs.size();
  }
};

struct S {
    ll ones, len;
};
S op(S a, S b) {
    return S(a.ones+b.ones, a.len+b.len);
}
S e() {
    return S(0, 0);
}

S mapping(bool f, S x) {
    if (f) x.ones = x.len - x.ones;
    return x;
}
bool composition(bool f, bool g) {
    return f ^ g;
}
bool id() {
    return false;
}

int main() {
    int q;
    cin >> q;
    
    vector<tuple<int, ll, ll>> qs;
    CC<ll> cc;
    rep(i, q) {
        int type; ll l, r;
        cin >> type >> l >> r;
        --l;
        
        qs.emplace_back(type, l, r);
        cc.add(l); 
        cc.add(r);
    }
    
    int n = cc.size()-1;
    vector<S> init(n);
    rep(i, n) {
        init[i] = S(0, cc[i+1]-cc[i]);
    }

    lazy_segtree<S, op, e, bool, mapping, composition, id> seg(init);

    for (auto& [type, l, r] : qs) {
        l = cc(l);
        r = cc(r);

        if (type == 1) {
            seg.apply(l, r, true);
        }
        else {
            cout << seg.prod(l, r).ones << '\n';
        }
    }
    
    return 0;
}

K. Multiset

将每个线段树节点维护的信息设为:

  • \(\text{cnt}\):区间内元素的个数。
  • \(\text{sum}\):区间内所有元素之和。
  • \(\text{ans}\):区间内元素按升序排列后,从下标 \(1\) 开始加权求和的结果

设左右子节点分别为 \(a,b\),由于右子节点中的所有元素在合并后的序列中都需要向后移动 \(a.cnt\) 个位置,因此:

\[\begin{aligned} cnt &= a.cnt+b.cnt,\\ sum &= a.sum+b.sum,\\ ans &= a.ans+b.ans+a.cnt \cdot b.sum \end{aligned} \]

将不同的数值离散化后,每个叶子节点维护某个数 \(x\) 的出现次数 \(c\)。该叶子的三个信息分别是:

\[cnt=c,\qquad sum=cx,\qquad ans=x\frac{c(c+1)}2. \]

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;
using P = pair<int, int>;
using mint = modint998244353;

// Coodinate Compression
template<typename T=int>
struct CC {
  bool initialized;
  vector<T> xs;
  CC(): initialized(false) {}
  void add(T x) { xs.push_back(x);}
  void init() {
    sort(xs.begin(), xs.end());
    xs.erase(unique(xs.begin(),xs.end()),xs.end());
    initialized = true;
  }
  int operator()(T x) {
    if (!initialized) init();
    return upper_bound(xs.begin(), xs.end(), x) - xs.begin() - 1;
  }
  T operator[](int i) {
    if (!initialized) init();
    return xs[i];
  }
  int size() {
    if (!initialized) init();
    return xs.size();
  }
};

struct S {
    int cnt;  
    mint sum, ans;  
};
S op(S a, S b) {
    return S(a.cnt+b.cnt, a.sum+b.sum, a.ans+b.ans+b.sum*a.cnt);
}
S e() {
    return S(0, 0, 0);
}

S toS(mint x, int c) {
    return S(c, x*c, x*c*(c+1)/2);
}

int main() {
    int n, q;
    cin >> n >> q;

    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    CC cc;
    rep(i, n) cc.add(a[i]);
    
    vector<P> qs;
    rep(i, q) {
        int t, x = 0;
        cin >> t;
        if (t != 3) {
            cin >> x;
            cc.add(x);
        }
        qs.emplace_back(t, x);
    }

    int m = cc.size();
    vector<int> freq(m);
    
    for (int x : a) {
        ++freq[cc(x)];
    }
    
    vector<S> init(m);
    rep(i, m) init[i] = toS(cc[i], freq[i]);

    segtree<S, op, e> seg(init);
    
    for (auto& [type, x] : qs) {
        if (type == 3) {
            cout << seg.all_prod().ans.val() << '\n';
        }
        else {
            x = cc(x);
            if (type == 1) ++freq[x];
            else --freq[x];
            seg.set(x, toS(cc[x], freq[x]));
        }
    }
    
    return 0;
}

L. Swap between Arrays

维护每个区间中 \(A,B,C\) 三个序列各自的区间和与最大前缀和。
区间交换只会交换对应序列的区间信息,而不会改变信息的计算方式;查询则利用 max_right 寻找第一个使前缀和大于 \(0\) 的位置。

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

const ll INF = 1e18;

struct S {
    ll sum[3];
    ll pref[3];
};
S op(S a, S b) {
    S c;
    rep(i, 3) {
        c.sum[i] = a.sum[i] + b.sum[i];
        c.pref[i] = max(a.pref[i], a.sum[i]+b.pref[i]);
    }
    return c;
}
S e() {
    S s;
    rep(i, 3) {
        s.pref[i] = -INF;
    }
    return s;
}

using F = array<int, 3>;
S mapping(F f, S s) {
    S t;
    rep(i, 3) {
        t.sum[i] = s.sum[f[i]];
        t.pref[i] = s.pref[f[i]];
    }
    return t;
} 
F composition(F f, F g) {
    F h;
    rep(i, 3) {
        h[i] = g[f[i]];
    }
    return h;
}
F id() { return F{0, 1, 2}; }

int main() {
    int n, q;
    cin >> n >> q;
    
    vector<int> a(n), b(n), c(n);
    rep(i, n) cin >> a[i];
    rep(i, n) cin >> b[i];
    rep(i, n) cin >> c[i];
    
    vector<S> d(n);
    rep(i, n) {
        d[i].sum[0] = d[i].pref[0] = a[i];
        d[i].sum[1] = d[i].pref[1] = b[i];
        d[i].sum[2] = d[i].pref[2] = c[i];
    }
    
    lazy_segtree<S, op, e, F, mapping, composition, id> seg(d);
    
    rep(qi, q) {
        int type;
        cin >> type;
        
        if (type == 4) {
            int i;
            cin >> i;
            --i;
            
            int r = seg.max_right(i, [](S s) {
                return s.pref[0] <= 0;
            });
            cout << r+1 << '\n';
        }
        else {
            int l, r;
            cin >> l >> r;
            --l;
            
            F f = id();
            if (type == 1) swap(f[0], f[1]);
            else if (type == 2) swap(f[1], f[2]);
            else swap(f[0], f[2]);
            
            seg.apply(l, r, f);
        }
    }
    
    return 0;
}

M. NAND

设 \(\text{full}=2^{30}-1\)。对于固定的 \(a\),有

\[\operatorname{NAND}(x,a)=\bigl(x\mathbin{\&}a\bigr)\mathbin{\oplus}\text{full}. \]

因此,每个操作都能表示成

\[f(x)=(x\mathbin{\&}m)\mathbin{\oplus}v. \]

对于单个元素 \(a\),对应的函数参数为 \((a,\text{full})\);恒等函数对应 \((\text{full},0)\)。

假设左区间对应函数 \(f\),右区间对应函数 \(g\),参数分别是 \((m_1,v_1)\) 和 \((m_2,v_2)\)。合并后执行的是先 \(f\),再 \(g\),即 \(g(f(x))\),其参数为

\[\begin{aligned} m&=m_1\mathbin{\&}m_2,\\ v&=(v_1\mathbin{\&}m_2)\mathbin{\oplus}v_2. \end{aligned} \]

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;
using P = pair<int, int>;

const int full = (1<<30)-1;

struct S {
    int m, v;
};
S op(S a, S b) {
    return S(a.m&b.m, (a.v&b.m)^b.v);
}
S e() {
    return S(full, 0);
}

int main() {
    int n, q;
    cin >> n >> q;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    segtree<S, op, e> seg(n);
    rep(i, n) seg.set(i, S(a[i], full));
    
    rep(qi, q) {
        int type;
        cin >> type;
        if (type == 1) {
            int p, x;
            cin >> p >> x;
            --p;
            
            a[p] = x;
            seg.set(p, S(x, full));
        }
        else {
            int l, r;
            cin >> l >> r;
            
            S f = seg.prod(l+1, r);
            int ans = (a[l]&f.m)^f.v;
            cout << ans << '\n';
        }
    }
    
    return 0;
}

N. Times Tables

只需对扫描线求矩形并的板子简单改改就行了
也就是将线段树维护的区间长度改为区间内列坐标之和

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using mint = modint998244353;

const int INF = 1001001001;

struct S {
    int mn;
    mint sum;
};
S op(S a, S b) {
    if (a.mn < b.mn) return a;
    if (a.mn > b.mn) return b;
    return S(a.mn, a.sum+b.sum);
}
S e() {
    return S(INF, 0);
}

S mapping(int f, S x) {
    x.mn += f;
    return x;
}
int composition(int f, int g) {
    return f+g;
}
int id() {
    return 0;
}

mint c2(mint x) {
    return x*(x+1)/2;
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    int n, q;
    cin >> n >> q;
    
    vector<vector<tuple<int, int, int>>> qs(n+1);
    rep(i, q) {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        --a; --b;
        qs[a].emplace_back(b, d, 1);
        qs[c].emplace_back(b, d, -1);
    }
    
    lazy_segtree<S, op, e, int, mapping, composition, id> seg(n);
    rep(j, n) {
        seg.set(j, S(0, j+1));
    }
    
    mint tot = c2(n);
    mint ans = tot*tot;
    rep(x, n+1) {
        for (auto& [l, r, sign] : qs[x]) {
            seg.apply(l, r, sign);
        }
        
        S s = seg.all_prod();
        mint now = tot;
        if (s.mn == 0) now -= s.sum;
        ans -= (x+1)*now;
    }

    cout << ans.val() << '\n';
    
    return 0;
}

O. Minimum Value

单调栈+历史和线段树
处理右端点 \(i=0,1,\ldots,N-1\)。对于每个左端点 \(j\),维护两个量:

  • mn[j]:当前最小值 \(\min(A_j,\ldots,A_i)\)。
  • hsum[j]:历史和 \(\sum\limits_{k=j}^{i}\min(A_j,\ldots,A_k)\)。

那么查询 \([l,r]\) 的答案恰好就是

\[\sum_{j=l}^{r}\operatorname{hsum}[j] \]

因为 \(\operatorname{hsum}[j]\) 已经统计了左端点为 \(j\)、右端点不超过当前 \(r\) 的所有子数组。

当加入 \(A_i\) 时,随着左端点 \(j\) 从右往左移动,\(\text{mn}[j]\) 单调不减。

使用单调栈维护最小值相同的连续区间。弹出所有满足 栈顶值 \(>= A_i\) 的元素,得到最左边界 \(j'\),然后将区间 \([j',i]\) 内所有位置的当前最小值赋为 \(A_i\) 。

完成当前最小值更新后,需要将所有已经加入的左端点的历史和加上当前最小值。

这里用线段树统一维护当前最小值和历史和。每个线段树节点保存两个区间和以及区间长度,懒标记表示对每个叶子执行

\[(m,h)\longmapsto(pm+q,\ h+um+v). \]

这样,区间赋值和历史累加都可以用同一套懒标记表示。

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;
using P = pair<int, int>;

struct S {
    ll mn, hsum;
    int len;
};
S op(S a, S b) {
    return S(a.mn+b.mn, a.hsum+b.hsum, a.len+b.len);
}
S e() {
    return S(0, 0, 0);
}

struct F {
    ll p, q, u, v;
};
S mapping(F f, S x) {
    return S(f.p*x.mn + f.q*x.len, x.hsum + f.u*x.mn + f.v*x.len, x.len);
}
F composition(F f, F g) {
    return F(f.p*g.p, f.p*g.q + f.q, g.u + f.u*g.p, g.v + f.u*g.q + f.v);
}
F id() {
    return F(1, 0, 0, 0);
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    int n, q;
    cin >> n >> q;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    vector<vector<P>> qs(n);
    rep(qi, q) {
        int l, r;
        cin >> l >> r;
        --l; --r;
        qs[r].emplace_back(l, qi);
    }
    
    lazy_segtree<S, op, e, F, mapping, composition, id> seg(n);
    rep(i, n) seg.set(i, S(0, 0, 1));
    
    vector<ll> ans(q);
    vector<P> st;
    rep(i, n) {
        int j = i;
        while (st.size() and st.back().first >= a[i]) {
            j = st.back().second;
            st.pop_back();
        }
        st.emplace_back(a[i], j);
        
        seg.apply(j, i+1, F(0, a[i], 0, 0));
        seg.apply(0, i+1, F(1, 0, 1, 0));
        
        for (auto& [l, qi] : qs[i]) {
            ans[qi] = seg.prod(l, i+1).hsum;
        }
    }
    
    rep(i, q) cout << ans[i] << '\n';
    
    return 0;
}

P. Connect the Dots

将圆剪开,把每条曲线看作区间 \([A_i,B_i]\)。

两条曲线相交,当且仅当存在 \(i<j\),满足

\[A_i<A_j<B_i<B_j. \]

因此,询问 \([l,r]\) 合法,当且仅当对应的区间两两不相交(允许包含关系),即构成一个 laminar family。


维护数组 \(C\),初始全部为 \(0\)。

为每条曲线 \([A_i,B_i]\) 随机分配一个 \(64\) 位哈希值 \(h_i\)。加入曲线时,将 \(h_i\) 异或到所有满足

\[A_i\le k<B_i \]

的位置 \(C_k\);删除时执行相同操作即可撤销。

考虑将新区间 \([a,b]\) 加入当前集合。已有区间 \([c,d]\) 对 \(C_a\) 和 \(C_{b-1}\) 的贡献不同,当且仅当它与新区间交错。因此,可以通过

\[\boxed{C_a=C_{b-1}} \]

判断新区间能否加入。用线段树维护差分数组,即可在 \(O(\log N)\) 时间内完成区间哈希更新和前缀查询。


定义 \(R_l\) 为以 \(l\) 为左端点的最大合法区间的右端点(半开区间)。

使用双指针维护当前合法区间 \([l,r)\):

  1. 不断尝试加入第 \(r\) 条曲线;若加入后仍合法,就令 \(r\gets r+1\)。
  2. 记录 \(R_l=r\)。
  3. 删除第 \(l\) 条曲线,继续处理下一个左端点。

每条曲线最多加入、删除各一次,因此预处理时间复杂度为 \(O(N\log N)\)。

对于询问 \([l,r)\),答案为

\[\boxed{r\le R_l} \]

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ull = unsigned long long;

ull op(ull a, ull b) { return a^b; }
ull e() { return 0; }

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    int n, q;
    cin >> n >> q;

    vector<int> a(n), b(n);
    rep(i, n) cin >> a[i] >> b[i];
    
    mt19937_64 rng(58);
    vector<ull> h(n);
    rep(i, n) h[i] = rng();
    
    segtree<ull, op, e> seg(2*n+1);

    auto upd = [&](int i) {
        seg.set(a[i], seg.get(a[i])^h[i]);
        seg.set(b[i], seg.get(b[i])^h[i]);
    };
    auto get = [&](int p) {
        return seg.prod(0, p+1);
    };
    auto check = [&](int i) {
        return get(a[i]) == get(b[i]-1);
    };

    vector<int> lim(n);
    int r = 0;
    rep(l, n) {
        while (r < n and check(r)) upd(r++);
        lim[l] = r;
        upd(l);
    }
    
    rep(qi, q) {
        int l, r;
        cin >> l >> r;
        --l;
        if (r <= lim[l]) puts("Yes");
        else puts("No");
    }
    
    return 0;
}

Q. Number of Moves

对于一段连续操作,设初始坐标为 \(x\),最终坐标为 \(f(x)\),获得的钱数为 \(g(x)\)。可以用四个整数 \((l,r,a,b)\) 描述这两个函数:

\[ f(x)= \begin{cases} l+a & x<l,\\ x+a & l\le x\le r,\\ r+a & x>r, \end{cases} \]

\[ g(x)=b-\max(0,l-x)-\max(0,x-r). \]

其中,\([l,r]\) 是最终坐标函数的线性区间,\(a\) 是坐标偏移量,\(b\) 是在该区间内获得的钱数。


设 \(X,Y\) 分别表示前后两段操作,合并后对应函数复合 \(f_Y\circ f_X\)。由于上述函数的形式在复合后仍然保持不变,因此可以继续用四元组表示。

具体地,将后一段的线性区间平移 \(-a_X\),再与前一段的线性区间求交。如果交集非空,新区间端点取交集端点,偏移量和基准收益分别相加;如果交集为空,则合并后的最终坐标为常数,需要根据两区间的相对位置确定新的四元组。


用线段树维护每个位置对应的四元组。修改时更新单点;查询区间时,通过线段树合并得到区间四元组 \((l,r,a,b)\),最终答案为

\[ \boxed{b-\max(0,l-x)-\max(0,x-r)}. \]

代码实现
#include <bits/stdc++.h>
#include <atcoder/all>
using namespace atcoder;
#define rep(i, n) for (int i = 0; i < (n); ++i)

using namespace std;
using ll = long long;

struct S {
    int l, r, a; ll b;
};

int m;

S op(S x, S y) {
    int l = y.l-x.a, r = y.r-x.a;
    if (x.r < l) return S(x.r, x.r, y.l+y.a-x.r, x.b+y.b-(l-x.r));
    if (r < x.l) return S(x.l, x.l, y.r+y.a-x.l, x.b+y.b-(x.l-r));
    return S(max(x.l, l), min(x.r, r), x.a+y.a, x.b+y.b);
}

S e() {
    return S(0, m, 0, 0);
}

S toS(int a, char c) {
    int d = min(a, m);
    if (c == '1') return S(0, m-d, d, d);
    return S(d, m, -d, d);
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    
    int n, q;
    string s;
    cin >> n >> m >> q >> s;
    
    vector<int> a(n);
    rep(i, n) cin >> a[i];
    
    segtree<S, op, e> seg(n);
    rep(i, n) seg.set(i, toS(a[i], s[i]));
    
    rep(qi, q) {
        int type;
        cin >> type;
        
        if (type == 1) {
            int i;
            cin >> i;
            --i;
            s[i] ^= 1;
            seg.set(i, toS(a[i], s[i]));
        }
        else {
            int x, l, r;
            cin >> x >> l >> r;
            --l;
            S z = seg.prod(l, r);
            ll ans = z.b-abs(x-clamp(x, z.l, z.r));
            cout << ans << '\n';
        }
    }
    
    return 0;
}