20260812 - 线段树码力大比拼总结

大家都说我学线段树学魔怔了,但确实分块扩展程度大于线段树大于树状数组。

线段树讲解推销一波 OTMZ 耗费 1145141919810 世纪写的文章

可以使用线段树的条件

  1. 节点信息必须能够合并,即知道左右孩子节点的完整信息后,能在可接受时间复杂度内得到父节点信息;
  2. 合并必须满足结合律。无论区间如何划分,最后合并得到的信息都相同;
  3. 懒标记必须能够直接作用于整段信息。如果只知道节点信息,却无法计算整段修改后的信息,就不能打这个懒标记;
  4. 本题的正解是线段树

写线段树之前的准备

写线段树之前,问自己几个问题

  1. 节点内存放什么内容
  2. 叶节点如何初始化
  3. 左右节点如何合并
  4. 懒标记如何作用于节点
  5. 懒标记如何合并

下面的题目按照上面问题的总结。

我的线段树过于封装了,一半如果标记不多的话还是正常写,但我发现封装的越好越好改!

今天屎山代码的数量大于了 \(\tan 90^\circ\)

我也懒得写时间复杂度了,不出意外都是 \(O(n\log n)\),出意外了再写。

A - Second Largest Query

题目评价:™最难的一题放在第一道,是想 cos [CSP-S 2020] 儒略日 吗?所以说要把所有题目看完 QAQ。

题目解析

  1. 节点内存放什么内容
  • 最大值、最大值的出现次数、次大值、次大值的出现次数。
  1. 叶节点如何初始化?
  • 将最大值设为自身,最大值出现次数为 \(1\) ,次大值和次大值的出现次数均设为 \(0\) 。
  1. 左右节点如何合并?
  • 首先开一个 vector 记录值和次数,然后如果找到相等的就加上,否则新加一个节点。
#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 2e5 + 7;
constexpr int P = 998244353;

int n, m, a[N];

struct Info {
    int maxv, smaxv;
    int maxcnt, smaxcnt;
};

struct Node {
    Info val;
} seg[N * 4];

Info operator + (const Info &A, const Info &B) {
    vector<pair<int, int>> v;
    auto add = [&](int val, int cnt) -> void {
        if (cnt == 0) return;
        for (auto &p : v) {
            if (p.first == val) { p.second += cnt; return; }
        }
        v.push_back({val, cnt});
    };
    add(A.maxv, A.maxcnt);
    add(A.smaxv, A.smaxcnt);
    add(B.maxv, B.maxcnt);
    add(B.smaxv, B.smaxcnt);
    sort(v.begin(), v.end(), greater<pair<int, int>>());
    Info C;
    C.maxv = v[0].first;
    C.maxcnt = v[0].second;
    if ((int)v.size() > 1) {
        C.smaxv = v[1].first;
        C.smaxcnt = v[1].second;
    } else {
        C.smaxv = 0;
        C.smaxcnt = 0;
    }
    return C;
}

void update(int id) {
    seg[id].val = seg[id * 2].val + seg[id * 2 + 1].val;
}

void build(int id, int l, int r) {
    if (l == r) {
        seg[id].val = {a[l], 0, 1, 0};
    } else {    
        int mid = (l + r) / 2;
        build(id * 2, l, mid);
        build(id * 2 + 1, mid + 1, r);
        update(id);
    }
}

void change(int id, int l, int r, int x, int v) {
    if (l == r) {
        seg[id].val.maxv = v;
    } else {
        int mid = (l + r) / 2;
        if (x <= mid) change(id * 2, l, mid, x, v);
        else change(id * 2 + 1, mid + 1, r, x, v);
        update(id);
    }
}

Info query(int id, int l, int r, int ql, int qr) {
    if (ql == l && qr == r) return seg[id].val;
    int mid = (l + r) / 2;
    if (qr <= mid) return query(id * 2, l, mid, ql, qr);
    else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
    else return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    build(1, 1, n);
    while (m--) {
        int op, l, r;
        scanf("%d%d%d", &op, &l, &r);
        if (op == 1) change(1, 1, n, l, r);
        else {
            auto ans = query(1, 1, n, l, r);
            printf("%lld\n", ans.smaxcnt);
        }   
    }
    return 0; 
}

B - Lazy Segment Tree

题目评价:真的很简单吗?

  1. 节点内存放什么内容
  • \(0\) 的个数,\(1\) 的个数,逆序对的个数顺序对的个数。
  1. 叶节点如何初始化
  • 判断是否是 \(0/1\),插入到对应的位置。
  1. 左右节点如何合并
  • 根据归并排序求逆序对,逆序对个数就是左边的个数加上右边的个数再加上跨越左右两边的逆序对,显然这个逆序对个数就是左边 \(1\) 的个数乘上右边 \(0\) 的个数,顺序对同理。
  1. 懒标记如何作用于节点
  • 交换所有的数,因为从 \(0\) 变成了 \(1\)
  1. 懒标记如何合并
  • 懒标记异或上 \(1\)
#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 2e5 + 7;
constexpr int P = 998244353;

int n, m, a[N];

struct Info {
    int cnt0, cnt1;
    ll rev, frev;
};

struct Tag {
    bool rev;
};

struct Node {
    Info val;
    Tag t;
    int siz;
} seg[N * 4];

Info operator + (const Info &A, const Info &B) {
    return {A.cnt0 + B.cnt0, A.cnt1 + B.cnt1, 
            A.rev + B.rev + 1LL * A.cnt1 * B.cnt0,
            A.frev + B.frev + 1LL * A.cnt0 * B.cnt1};
}

void update(int id) {
    seg[id].val = seg[id * 2].val + seg[id * 2 + 1].val;
}

void build(int id, int l, int r) {
    seg[id].siz = r - l + 1;
    if (l == r) {
        seg[id].val = {!a[l], a[l], 0, 0};
    } else {    
        int mid = (l + r) / 2;
        build(id * 2, l, mid);
        build(id * 2 + 1, mid + 1, r);
        update(id);
    }
}

void settag(int id, Tag v) {
    swap(seg[id].val.cnt0, seg[id].val.cnt1);
    swap(seg[id].val.rev, seg[id].val.frev);
    seg[id].t.rev ^= 1;
}

void pushdown(int id) {
    if (seg[id].t.rev) {
        settag(id * 2, seg[id].t);
        settag(id * 2 + 1, seg[id].t);
        seg[id].t.rev ^= 1;
    }
}

void modify(int id, int l, int r, int ql, int qr, Tag v) {
    if (ql == l && qr == r) {
        settag(id, v);
    } else {
        int mid = (l + r) / 2;
        pushdown(id);
        if (qr <= mid) modify(id * 2, l, mid, ql, qr, v);
        else if (ql > mid) modify(id * 2 + 1, mid + 1, r, ql, qr, v);
        else modify(id * 2, l, mid, ql, mid, v), modify(id * 2 + 1, mid + 1, r, mid + 1, qr, v);
        update(id);
    }
}

Info query(int id, int l, int r, int ql, int qr) {
    if (ql == l && qr == r) return seg[id].val;
    int mid = (l + r) / 2;
    pushdown(id);
    if (qr <= mid) return query(id * 2, l, mid, ql, qr);
    else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
    else return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    build(1, 1, n);
    while (m--) {
        int op, l, r;
        scanf("%d%d%d", &op, &l, &r);
        if (op == 1) { 
            modify(1, 1, n, l, r, {1});
        } else {
            auto ans = query(1, 1, n, l, r);
            printf("%lld\n", ans.rev);
        }
    }
    return 0;
}

C - Two Sequence Queries

题目评价:其实就是线段树 \(2\) 的改变版。

  1. 节点内存放什么内容
  • 区间 \(A\)\(B\) 的和,区间 \(A \times B\) 的和。
  1. 叶节点如何初始化
  • 分别是 \(A,B,A\times B\)
  1. 左右节点如何合并
  • 直接相加。
  1. 懒标记如何作用于节点
    • 注意到,\(\begin{aligned}\sum_{i=l}^r(A_i+X)(B_i+Y) &= A_iB_i+A_iY+B_iX+XY \\&=\sum_{i=l}^rA_iB_i+Y\sum_{i=l}^rA_i+X\sum_{i=l}^rB_i+\sum_{i=l}^rXY\\&=\sum_{i=l}^rA_iB_i+Y\sum_{i=l}^rA_i+X\sum_{i=l}^rB_i+(r-l+1)\times XY \end{aligned}\)直接下传就好了。
  2. 懒标记如何合并
  • 和求和一样。
#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 2e5 + 7;
constexpr int P = 998244353;

int n, q;
ll a[N], b[N];

struct Info {
    ll sa, sb, sab;
};

struct Tag {
    ll ta, tb;
};

struct Node {
    Info val;
    Tag t;
    ll siz;
} seg[N * 4];

Info operator + (const Info &A, const Info &B) {
    Info C;
    C.sa = (A.sa + B.sa) % P;
    C.sb = (A.sb + B.sb) % P;
    C.sab = (A.sab + B.sab) % P;
    return C;
}

void update(int id) {
    seg[id].val = seg[id * 2].val + seg[id * 2 + 1].val;
}

void build(int id, int l, int r) {
    seg[id].siz = r - l + 1;
    if (l == r) {
        seg[id].val = {a[l], b[l], (1LL * a[l] * b[l]) % P};
    } else {    
        int mid = (l + r) / 2;
        build(id * 2, l, mid);
        build(id * 2 + 1, mid + 1, r);
        update(id);
    }
}

void settag(int id, Tag t) {
    seg[id].val.sab = (seg[id].val.sab % P + (1LL * t.tb * seg[id].val.sa) % P + (1LL * t.ta * seg[id].val.sb) % P + 1LL * seg[id].siz % P * t.ta % P * t.tb % P) % P;
    seg[id].val.sa = (seg[id].val.sa + 1LL * t.ta * seg[id].siz % P) % P;
    seg[id].val.sb = (seg[id].val.sb + 1LL * t.tb * seg[id].siz % P) % P;
    seg[id].t.ta = (seg[id].t.ta + t.ta) % P;
    seg[id].t.tb = (seg[id].t.tb + t.tb) % P;
}

void pushdown(int id) {
    settag(id * 2, seg[id].t);
    settag(id * 2 + 1, seg[id].t);   
    seg[id].t = {0, 0};
}

void modify(int id, int l, int r, int ql, int qr, Tag v) {
    if (ql == l && qr == r) {
        settag(id, v);
    } else {
        int mid = (l + r) / 2;
        pushdown(id);
        if (qr <= mid) modify(id * 2, l, mid, ql, qr, v);
        else if (ql > mid) modify(id * 2 + 1, mid + 1, r, ql, qr, v);
        else modify(id * 2, l, mid, ql, mid, v), modify(id * 2 + 1, mid + 1, r, mid + 1, qr, v);
        update(id);
    }
}

Info query(int id, int l, int r, int ql, int qr) {
    if (ql == l && qr == r) return seg[id].val;
    int mid = (l + r) / 2;
    pushdown(id);
    if (qr <= mid) return query(id * 2, l, mid, ql, qr);
    else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
    else return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}

int main() {
    scanf("%d%d", &n, &q);
    for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
    for (int i = 1; i <= n; i++) scanf("%lld", &b[i]);
    build(1, 1, n);
    while (q--) {
        int op, l, r, x;
        scanf("%d%d%d", &op, &l, &r);
        if (op == 1) {
           scanf("%d", &x);
           modify(1, 1, n, l, r, {x, 0});
        } else if (op == 2) {
            scanf("%d", &x);
            modify(1, 1, n, l, r, {0, x});
        } else {
            auto ans = query(1, 1, n, l, r);
            printf("%lld\n", ans.sab);
        }
    }
    return 0;
}

D - Parenthesis Checking

思路:将括号序列转变为同样长度的数组,其中 ( 为 \(1\), 而 ) 为 \(-1\)

  1. 节点存放什么内容?
  • 前缀和,前缀和最小值
  1. 叶节点如何初始化?
  • 总和和前缀最小值都是自身。
  1. 左右节点如何合并?
  • 总和直接加上,否则和前面的求最小值。
#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 5e5 + 7;
constexpr int P = 998244353;

int n, m;
char a[N];

struct Info {
    int sum, minv;
};

struct Node {
    Info val;
} seg[N * 4];

Info operator + (const Info &A, const Info &B) {
    Info C;
    C.sum = A.sum + B.sum;
    C.minv = min({A.minv, A.sum + B.minv});
    return C;
}

void update(int id) {
    seg[id].val = seg[id * 2].val + seg[id * 2 + 1].val;
}

void build(int id, int l, int r) {
    if (l == r) {
        seg[id].val = {(a[l] == '(' ? 1 : -1), (a[l] == '(' ? 1 : -1)};
    } else {    
        int mid = (l + r) / 2;
        build(id * 2, l, mid);
        build(id * 2 + 1, mid + 1, r);
        update(id);
    }
}

void change(int id, int l, int r, int x, int v) {
    if (l == r) {
        seg[id].val.sum = v;
        seg[id].val.minv = v;
    } else {
        int mid = (l + r) / 2;
        if (x <= mid) change(id * 2, l, mid, x, v);
        else change(id * 2 + 1, mid + 1, r, x, v);
        update(id);
    }
}

Info query(int id, int l, int r, int ql, int qr) {
    if (ql == l && qr == r) return seg[id].val;
    int mid = (l + r) / 2;
    if (qr <= mid) return query(id * 2, l, mid, ql, qr);
    else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
    else return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}

int main() {
    scanf("%d%d", &n, &m);
    scanf("%s", a + 1);
    build(1, 1, n);
    // for (int i = 1; i <= n; i++) printf("%d ", query(1, 1, n, 1, i).minv);
    while (m--) {
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);
        if (op == 1) {
            int l = x, r = y;
            swap(a[l], a[r]);
            change(1, 1, n, x, ((a[l] == '(') ? 1 : -1));
            change(1, 1, n, y, ((a[r] == '(') ? 1 : -1));
        } else {
            auto ans = query(1, 1, n, x, y);
            puts(ans.sum == 0 && ans.minv >= 0 ? "Yes" : "No");
        }
    }
    return 0;
}

E - Distinct Characters Queries

  1. 节点存放什么内容?
  • 每个字符开一个线段树。
  1. 叶节点如何初始化?
  • 对应的位置加上。
  1. 左右节点如何合并?
  • 直接求和

你知道吗?一开始想偏了想的可持久化。

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 5e5 + 7;
constexpr int P = 998244353;

int n, m;
char s[N];

struct Info {
    int sum;
};

struct Node {
    Info val;
} seg[N * 4][30];

Info operator + (const Info &A, const Info &B) {
    return {A.sum + B.sum};
}

int idx = 0;

void update(int id) {
    seg[id][idx].val = seg[id * 2][idx].val + seg[id * 2 + 1][idx].val;
}

void build(int id, int l, int r, char ch) {
    if (l == r) {
        seg[id][idx].val = {s[l] == ch ? 1 : 0};
    } else {    
        int mid = (l + r) / 2;
        build(id * 2, l, mid, ch);
        build(id * 2 + 1, mid + 1, r, ch);
        update(id);
    }
}

void change(int id, int l, int r, int x, int v) {
    if (l == r) {
        seg[id][idx].val.sum = v;
    } else {
        int mid = (l + r) / 2;
        if (x <= mid) change(id * 2, l, mid, x, v);
        else change(id * 2 + 1, mid + 1, r, x, v);
        update(id);
    }
}

Info query(int id, int l, int r, int ql, int qr) {
    if (ql == l && qr == r) return seg[id][idx].val;
    int mid = (l + r) / 2;
    if (qr <= mid) return query(id * 2, l, mid, ql, qr);
    else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
    else return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}

int main() {
    scanf("%s", s + 1);
    n = strlen(s + 1);
    scanf("%d", &m);
    for (int i = 0; i < 26; i++) {
        build(1, 1, n, i + 'a');
        ++idx;
    }
    while (m--) {
        int op, l;
        char y;
        scanf("%d%d", &op, &l);
        if (op == 1) {
            scanf(" %c", &y);
            idx = s[l] - 'a';
            change(1, 1, n, l, 0);
            idx = y - 'a';
            s[l] = y;
            change(1, 1, n, l, 1);
        } else {
            int r;
            scanf("%d", &r);
            int res = 0;
            for (int i = 0; i < 26; i++) {
                idx = i;
                auto ans = query(1, 1, n, l, r);
                if (ans.sum) ++res;
            }
            printf("%d\n", res);
        }
    }
    return 0;
}

F - Segment Tree

  1. 节点存放什么内容?
  • 区间最大值
  1. 叶节点如何初始化?
  • 就是叶子结点本身的值。
  1. 左右节点如何合并?
  • 求最大值
#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) (x).begin(), (x).end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 2e5 + 7;
constexpr int P = 998244353;

int n, m, a[N];

struct Info {
    int maxv;
};

struct Node {
    Info val;
} seg[N * 4];

Info operator + (const Info &A, const Info &B) {
    return {max(A.maxv, B.maxv)};
}

void update(int id) {
    seg[id].val = seg[id * 2].val + seg[id * 2 + 1].val;
}

void build(int id, int l, int r) {
    if (l == r) {
        seg[id].val = {a[l]};
    } else {    
        int mid = (l + r) / 2;
        build(id * 2, l, mid);
        build(id * 2 + 1, mid + 1, r);
        update(id);
    }
}

void change(int id, int l, int r, int x, int v) {
    if (l == r) {
        seg[id].val.maxv = v;
    } else {
        int mid = (l + r) / 2;
        if (x <= mid) change(id * 2, l, mid, x, v);
        else change(id * 2 + 1, mid + 1, r, x, v);
        update(id);
    }
}

Info query(int id, int l, int r, int ql, int qr) {
    if (ql == l && qr == r) return seg[id].val;
    int mid = (l + r) / 2;
    if (qr <= mid) return query(id * 2, l, mid, ql, qr);
    else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
    else return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}


int search(int id, int l, int r, int ql, int qr, int d) {
	if (l == ql && r == qr) {
		if (seg[id].val.maxv >= d) {
			if (l == r) return l;
			int mid = (l + r) / 2;
			if (seg[id * 2].val.maxv >= d) {
				return search(id * 2, l, mid, ql, mid, d);
			} else {
				return search(id * 2 + 1, mid + 1, r, mid + 1, qr, d);
			}
		} else {
			return -1;
		}
	} else {
		int mid = (l + r) / 2;
		if (qr <= mid) return search(id * 2, l, mid, ql, qr, d);
		else if (ql > mid) return search(id * 2 + 1, mid + 1, r, ql, qr, d);
		else {
			int pos = search(id * 2, l, mid, ql, mid, d);
			if (pos == -1)
				pos = search(id * 2 + 1, mid + 1, r, mid + 1, qr, d);
			return pos;
		}
	}
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    build(1, 1, n);
    while (m--) {
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);
        if (op == 1) change(1, 1, n, x, y);
        else if (op == 2) {
            auto ans = query(1, 1, n, x, y);
            printf("%d\n", ans.maxv);
        } else {
            int pos = search(1, 1, n, x, n, y);
            printf("%d\n", pos == -1 ? n + 1 : pos);
        }   
    }
    return 0; 
}

不会线段树上二分的可以看看这里。

G - Vacation Query

  1. 节点存放什么内容?
  •   struct Info {
          ll mcnt1, mpre1, msuf1;
          ll mcnt0, mpre0, msuf0;
          bool ls, rs, forallone, forallzero; 
      };
    
  1. 叶节点如何初始化?
  •  if (a[l]) {
     	seg[id].val = {1, 1, 1, 0, 0, 0, 1, 1, 1, 0};
     } else {
     	seg[id].val = {0, 0, 0, 1, 1, 1, 0, 0, 0, 1};
     }
    
  1. 左右节点如何合并?
  • Info operator + (const Info &l, const Info &r) {
      Info A;
      A.mcnt1 = max({l.mcnt1, r.mcnt1, (l.rs == 1) * l.msuf1 + (r.ls == 1) * r.mpre1});
      A.mcnt0 = max({l.mcnt0, r.mcnt0, (l.rs == 0) * l.msuf0 + (r.ls == 0) * r.mpre0});
      A.mpre1 = l.mpre1 + (l.rs == 1 && r.ls == 1) * l.forallone * r.mpre1;
      A.mpre0 = l.mpre0 + (l.rs == 0 && r.ls == 0) * l.forallzero * r.mpre0;
      A.msuf1 = r.msuf1 + (l.rs == 1 && r.ls == 1) * r.forallone * l.msuf1;
      A.msuf0 = r.msuf0 + (l.rs == 0 && r.ls == 0) * r.forallzero * l.msuf0;
      A.forallone = (l.forallone & r.forallone & (l.rs == r.ls));
      A.forallzero = (l.forallzero & r.forallzero & (l.rs == r.ls));
      A.ls = l.ls; 
      A.rs = r.rs;
      return A;
    

}

4. 懒标记如何作用于节点?
- ```cpp
void settag(int id, Tag v) {
  swap(seg[id].val.mcnt1, seg[id].val.mcnt0);
  swap(seg[id].val.mpre1, seg[id].val.mpre0);
  swap(seg[id].val.msuf1, seg[id].val.msuf0);
  swap(seg[id].val.forallone, seg[id].val.forallzero); 
  seg[id].val.ls ^= 1, seg[id].val.rs ^= 1;
  seg[id].t.rev ^= 1;
}
  1. 懒标记如何合并?
  • 异或 \(1\),同 B 题。

代码不用放了吧。

H - 上帝造题的七分钟 2 / 花神游历各国

包括 I 我们并不按照传统方式。

我们注意到 \(a_i\) 只有 \(10^{12}\),而且开一次根号就变成 \(10^6\)

所以我们通过人类智慧发现如果区间的最大值都等于 \(1\),就没有必要去递归了。

大概就是这样,每个点最多只有 \(6\) 次访问,即还是保持原来的复杂度。

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) x.begin(), x.end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 5e5 + 7;
constexpr int P = 998244353;

int n, q;
ll a[N]; 

struct Info {
	ll sum, sq;
};

struct Tag {
    
};

Info operator + (const Info &l, const Info &r) {
	return {l.sum + r.sum};
}

struct Node {
	Info val;
    Tag t;
} seg[N * 4];

void update(int id) {
	seg[id].val = seg[id * 2].val + seg[id * 2 + 1].val;
    seg[id].val.sq = max(seg[id * 2].val.sq, seg[id * 2 + 1].val.sq);
}

void build(int id, int l, int r) {
	if (l == r) {
		seg[id].val = {a[l], a[l]};
	} else {
		int mid = (l + r) / 2;
		build(id * 2, l, mid);
		build(id * 2 + 1, mid + 1, r);
		update(id);
	}
} 

Info query(int id, int l, int r, int ql, int qr) {
	if (l == ql && r == qr) return seg[id].val;
	int mid = (l + r) / 2;
	if (qr <= mid) return query(id * 2, l, mid, ql, qr);
	else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
	else return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}

void modify(int id, int l, int r, int ql, int qr) {
    if (r < ql || l > qr) return;
    if (l == r) {
        seg[id].val.sum = sqrtl(seg[id].val.sum), seg[id].val.sq = sqrtl(seg[id].val.sq);
        // printf("a[l] = %d\n", a[l]);
    } else {
        int mid = (l + r) / 2;
        if (seg[id * 2].val.sq > 1)
            modify(id * 2, l, mid, ql, qr); //  && seg[id * 2].val.sq > 1
        if (seg[id * 2 + 1].val.sq > 1)
            modify(id * 2 + 1, mid + 1, r, ql, qr); //  && seg[id * 2 + 1].val.sq > 1
        update(id);
    }
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
    scanf("%d", &q);
	build(1, 1, n); 
	while (q--) {
		int type;
		scanf("%d", &type);
		if (type == 0) {
			int l, r;
			scanf("%d%d", &l, &r);
            if (l > r) swap(l, r);
			modify(1, 1, n, l, r);
		} else {
			int l, r;
			scanf("%d%d", &l, &r);
            if (l > r) swap(l, r);
			auto ans = query(1, 1, n, l, r);
			printf("%lld\n", ans.sum);
		}
	}
	return 0;
}

I - The Child and Sequence

我们充分发扬人类的智慧,由于取模之后应该不是很大,所以我们维护一个最大值,如果最大值小于模数,就不用递归了。

大概就是这样,每个点最多有 \(\log\) 次访问,所以复杂度乘上了一个 \(\log\)

#include <bits/stdc++.h>

using namespace std;
#define ll long long
#define ull unsigned long long
#define db double
#define all(x) x.begin(), x.end()
#define inf (1 << 30)
#define lnf (1LL << 60)
typedef pair<int, int> PII;
constexpr int N = 5e5 + 7;
constexpr int P = 998244353;

int n, q;
ll a[N]; 

struct Info {
	ll sum, sq;
};

struct Tag {
    
};

Info operator + (const Info &l, const Info &r) {
	return {l.sum + r.sum, max(l.sq, r.sq)};
}

struct Node {
	Info val;
    Tag t;
} seg[N * 4];

void update(int id) {
	seg[id].val = seg[id * 2].val + seg[id * 2 + 1].val;
    // seg[id].val.sq = max(seg[id * 2].val.sq, seg[id * 2 + 1].val.sq);
}

void build(int id, int l, int r) {
	if (l == r) {
		seg[id].val = {a[l], a[l]};
	} else {
		int mid = (l + r) / 2;
		build(id * 2, l, mid);
		build(id * 2 + 1, mid + 1, r);
		update(id);
	}
} 

Info query(int id, int l, int r, int ql, int qr) {
	if (l == ql && r == qr) return seg[id].val;
	int mid = (l + r) / 2;
	if (qr <= mid) return query(id * 2, l, mid, ql, qr);
	else if (ql > mid) return query(id * 2 + 1, mid + 1, r, ql, qr);
	else return query(id * 2, l, mid, ql, mid) + query(id * 2 + 1, mid + 1, r, mid + 1, qr);
}

void modify(int id, int l, int r, int ql, int qr, int p) {
    if (r < ql || l > qr) return;
    if (l == r) {
        seg[id].val.sum %= p, seg[id].val.sq %= p;
        // printf("a[l] = %lld\n", a[l]);
    } else {
        int mid = (l + r) / 2;
        if (seg[id * 2].val.sq >= p)
            modify(id * 2, l, mid, ql, qr, p); //  && seg[id * 2].val.sq > 1
        if (seg[id * 2 + 1].val.sq >= p)
            modify(id * 2 + 1, mid + 1, r, ql, qr, p); //  && seg[id * 2 + 1].val.sq > 1
        update(id);
    }
}

void change(int id, int l, int r, int x, int v) {
    if (l == r) {
        seg[id].val.sum = v;
        seg[id].val.sq = v;
    } else {
        int mid = (l + r) / 2;
        if (x <= mid) change(id * 2, l, mid, x, v);
        else change(id * 2 + 1, mid + 1, r, x, v);
        update(id);
    }
}

int main() {
	scanf("%d%d", &n, &q);
	for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	build(1, 1, n); 
	while (q--) {
		int type;
		scanf("%d", &type);
		if (type == 3) {
			int k, x;
			scanf("%d%d", &k, &x);
			change(1, 1, n, k, x);
		} else if (type == 2) {
			int l, r, x;
			scanf("%d%d%d", &l, &r, &x);
            modify(1, 1, n, l, r, x);
		} else {
            int l, r;
			scanf("%d%d", &l, &r);
            auto ans = query(1, 1, n, l, r);
            printf("%lld\n", ans.sum);
        }
        // for (int i = 1; i <= n; i++) printf("%lld ", query(1, 1, n, i, i).sum); puts("");
	}
	return 0;
}

总结

  1. 测完样例后提交之前一定要手搓一些边界数据,比如全是 \(1\),只有 \(1\) 等。
  2. 如果有懒标记,在查询的时候也要下传。

首 A 好吃。

让总结更方便的方法

因为 RMX 巨佬每次第一个交总结,所以题目的链接可以从他那儿复制,markdown 编译器会自动转换。

posted @ 2026-08-12 22:10  AKCoder  阅读(20)  评论(0)    收藏  举报