树状数组 (Binary Indexed Tree) & 线段树 (Segement Tree)

持续更新中...

一、Template

1、洛谷P3372 【模板】线段树 1

区间修改,区间查询

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define ls (p << 1) //左半区间
#define rs (p << 1 | 1) //右半区间
#define mid ((l + r) / 2)

const int N = 1e5 + 10;
int n, m, a[N];
struct node { int sum, tag; } t[N << 2];

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

void pushUp(int p) { t[p].sum = t[ls].sum + t[rs].sum; } //向上修改线段树

//下传延迟标记
void pushDown(int p, int l, int r) {
    t[ls].sum += (mid - l + 1) * t[p].tag, t[rs].sum += (r - mid) * t[p].tag;
    t[ls].tag += t[p].tag, t[rs].tag += t[p].tag;
    t[p].tag = 0;
}

void build(int p = 1, int l = 1, int r = n) {
    if (l == r) { t[p].sum = a[l]; return; }
    build(ls, l, mid), build(rs, mid + 1, r);
    pushUp(p);
}

void modify(int ql, int qr, int k, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) {
        t[p].sum += (r - l + 1) * k, t[p].tag += k;
        return;
    }
    pushDown(p, l, r);
    if (ql <= mid) modify(ql, qr, k, ls, l, mid);
    if (qr > mid) modify(ql, qr, k, rs, mid + 1, r);
    pushUp(p);
}

int query(int ql, int qr, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) return t[p].sum;
    pushDown(p, l, r); int ans = 0LL;
    if (ql <= mid) ans += query(ql, qr, ls, l, mid);
    if (qr > mid) ans += query(ql, qr, rs, mid + 1, r);
    return ans;
}

signed main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    build();
    while (m--) {
        int opt, x, y, k;
        opt = read(), x = read(), y = read();
        if (opt ==1) {
            k = read();
            modify(x, y, k);
        }
        else printf("%lld\n", query(x, y));
    }
    return 0;
}

2、洛谷P3373 【模板】线段树 2

区间修改,区间查询。先乘后加。在运算中要及时对m取余避免overflow。

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) / 2)

const int N = 1e5 + 10;
int n, q, m, a[N];
struct node { int sum, add, mul; } t[N << 2];

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

void pushUp(int p) { t[p].sum = (t[ls].sum + t[rs].sum) % m; }

//下传延迟标记
void pushDown(int p, int l, int r) {
    t[ls].sum = (t[ls].sum * t[p].mul % m + (mid - l + 1) * t[p].add % m) % m;
    t[rs].sum = (t[rs].sum * t[p].mul % m + (r - mid) * t[p].add % m) % m;
    t[ls].mul = t[ls].mul * t[p].mul % m, t[rs].mul = t[rs].mul * t[p].mul % m;
    t[ls].add = (t[ls].add * t[p].mul % m + t[p].add) % m;
    t[rs].add = (t[rs].add * t[p].mul % m + t[p].add) % m;
    t[p].add = 0LL, t[p].mul = 1LL;
}

void build(int p = 1, int l = 1, int r = n) {
    t[p].mul = 1LL; //任何数乘1都不会改变结果,所以初始标记应该为1
    if (l == r) { t[p].sum = a[l] % m; return; }
    build(ls, l, mid), build(rs, mid + 1, r);
    pushUp(p);
}

void ad(int ql, int qr, int k, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) {
        t[p].sum = (t[p].sum + (r - l + 1) * k % m) % m;
        t[p].add = (t[p].add + k) % m;
        return;
    }
    pushDown(p, l, r);
    if (ql <= mid) ad(ql, qr, k, ls, l, mid);
    if (qr > mid) ad(ql, qr, k, rs, mid + 1, r);
    pushUp(p);
}

void ml(int ql, int qr, int k, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) {
        t[p].sum = t[p].sum * k % m;
        t[p].add = t[p].add * k % m;
        t[p].mul = t[p].mul * k % m;
        return;
    }
    pushDown(p, l, r);
    if (ql <= mid) ml(ql, qr, k, ls, l, mid);
    if (qr > mid) ml(ql, qr, k, rs, mid + 1, r);
    pushUp(p);
}

int query(int ql, int qr, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) return t[p].sum % m;
    pushDown(p, l, r); int ans = 0LL;
    if (ql <= mid) ans = (ans + query(ql, qr, ls, l, mid)) % m;
    if (qr > mid) ans = (ans + query(ql, qr, rs, mid + 1, r)) % m;
    return ans % m;
}

signed main() {
    n = read(), q = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    build();
    while (q--) {
        int opt, x, y, k;
        opt = read(), x = read(), y = read();
        if (opt == 1) { k = read(); ml(x, y, k); }
        if (opt == 2) { k = read(); ad(x, y, k); }
        if (opt == 3) printf("%lld\n", query(x, y));
    }
    return 0;
}

3、P3374 【模板】树状数组 1

本题由于是单点修改,所以每次修改都是从最底层开始的,因此也不需要pushDown函数和打延迟标记,modify函数的递归终止条件也有所变化。对于区间修改的线段树来说build函数的终止条件应为ql \leqslant l \&\& r \leqslant qr,但是单点修改相当于是区间左右边界都相等的区间修改,即ql=qr,所以递归终止条件可以简写为l=r

本题显然用树状数组来写会更简单,而且运行效率也会更快(省了将近一半时间)

(1)树状数组写法

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int N=5e5+10;
int n, m, a[N], t[N], opt, x, y;

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c <'0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = x * 10 + c -'0', c = getchar();
    return x * f;
}

int lowbit(int x) { return x & (-x); }

void modify(int x, int k) {
    while (x <= n) {
        t[x] += k;
        x += lowbit(x);
    }
}

// 表示[1,x]的和
int query(int x) {
    int oup = 0LL;
    while (x) {
        oup += t[x];
        x -= lowbit(x);
    }
    return oup;
}

signed main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++) {
        a[i] = read();
        modify(i, a[i]);
    }
    while (m--) {
        opt = read(), x = read(), y = read();
        if (opt == 1) modify(x, y);
        if (opt == 2) printf("%lld\n", query(y) - query(x - 1));
    }
    return 0;
}

(2) 线段树写法

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) / 2)

const int N = 5e5 + 10;
int n, m, a[N];
struct node { int sum; } t[N << 2];

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

void pushUp(int p) { t[p].sum = t[ls].sum + t[rs].sum; }

void build(int p = 1, int l = 1, int  r = n) {
    if (l == r) { t[p].sum = a[l]; return; }
    build(ls, l, mid), build(rs, mid + 1, r);
    pushUp(p);
}

void modify(int q, int k, int p = 1, int l = 1, int r = n) {
    if (l == r) { t[p].sum += k; return; }
    if (q <= mid) modify(q, k, ls, l, mid);
    if (q > mid) modify(q, k, rs, mid + 1, r);
    pushUp(p);
}

int query(int ql, int qr, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) return t[p].sum;
    int ans = 0LL;
    if (ql <= mid) ans += query(ql, qr, ls, l, mid);
    if (qr > mid) ans += query(ql, qr, rs, mid + 1, r);
    return ans; 
}

signed main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    build();
    while (m--) {
        int opt, x, y;
        opt = read(), x = read(), y = read();
        if (opt == 1) modify(x, y);
        else printf("%lld\n", query(x, y));
    }
    return 0;
}

4、P3368 【模板】树状数组 2

本题虽然要进行区间修改,但是由于只需要进行区间查询所以也可以用树状数组来写。

我们可以用一个b数组来存a数组的差(b[i] = a[i] - a[i - 1]),对a数组的[x,y]区间加上k,相当于对b数组的第x位加k,第y + 1为减k

(1)树状数组写法

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int N = 5e5 + 10;
int n, m, a[N], b[N], t[N];

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <='9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

int lowbit(int x) { return x & (-x); }

void modify(int x, int k) {
    while (x <= n) {
        t[x] += k;
        x += lowbit(x);
    }
}

int query(int x) {
    int oup = 0LL;
    while (x) {
        oup +=t[x];
        x -= lowbit(x);
    }
    return oup;
}

signed main() {
    n = read(), m = read();
    for (int i =1; i <= n; i++) {
        a[i] = read();
        b[i] = a[i] - a[i-1];
        modify(i, b[i]);
    }
    while (m--) {
        int opt, x, y, k;
        opt = read();
        if (opt == 1) {
            x = read(), y = read(), k = read();
            modify(x, k), modify(y + 1, -k);
        }
        if (opt == 2) {
            x = read();
            printf("%lld\n", query(x));
        }
    }
    return 0;
}

(2)线段树写法

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) >> 1)

const int N = 5e5 + 10;
int n, m, a[N];
struct node { int sum, tag; } t[N << 2];

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >='0' && c <='9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

void pushUp(int p) { t[p].sum = t[ls].sum + t[rs].sum; }

void pushDown(int p, int l, int r) {
    if (t[p].tag) {
        t[ls].sum += (mid - l + 1) * t[p].tag, t[rs].sum += (r - mid) * t[p].tag;
        t[ls].tag += t[p].tag, t[rs].tag += t[p].tag;
        t[p].tag = 0;
    }
}

void build(int p = 1, int l = 1, int  r = n) {
    if (l == r) { t[p].sum = a[l]; return; }
    build(ls, l, mid), build(rs, mid + 1, r);
    pushUp(p);
}

void modify(int ql, int qr, int k, int p = 1, int l = 1, int  r = n) {
    if (ql <= l && r <= qr) {
        t[p].sum += (r - l + 1) * k, t[p].tag += k;
        return;
    }
    pushDown(p, l, r);
    if (ql <= mid) modify(ql, qr, k, ls, l, mid);
    if (qr > mid) modify(ql, qr, k, rs, mid + 1, r);
    pushUp(p);
}

int query(int q, int p = 1, int l = 1, int  r = n) {
    if (l == r) return t[p].sum;
    pushDown(p, l, r); int ans = 0LL;
    if (q <= mid) ans += query(q, ls, l, mid);
    if (q > mid) ans += query(q, rs, mid + 1, r);
    return ans;
}

signed main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    build();
    while (m--) {
        int opt, x, y, k;
        opt = read(), x = read();
        if (opt == 1) {
            y = read(), k = read();
            modify(x, y, k);
        }
        if (opt == 2) printf("%lld\n", query(x));
    }
    return 0;
}

二、Practice

1、P1908 逆序对

利用树状数组/线段树:首先对所有数字由大到小排序,相同大小的数字则原先排在后面的放到前面,接着从头开始遍历,统计在该数字位置之前出现过的数字数量。

利用归并排序:利用归并排序将该列数字从大到小排序,并同时统计逆序对数量

(1)树状数组写法

#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N = 5e5 + 10;
int n, ans = 0LL, t[N];
struct node { int m, num; } a[N]; // m存读入的数字,num存数字的位置

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >='0' && c <='9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

bool cmp(node x, node y)
{
    if (x.m == y.m) return x.num > y.num;
    return x.m > y.m;
}

int lowbit(int x) { return x & (-x); }

void add(int x, int k)
{
    while (x <= n)
    {
        t[x] += k;
        x += lowbit(x);
    }
}
int sum(int x)
{
    int oup = 0LL;
    while (x > 0)
    {
        oup += t[x];
        x -= lowbit(x);
    }
    return oup;
}

signed main()
{
    n = read();
    for (int i = 1; i <= n; i++)
    {
        a[i].m = read();
        a[i].num = i;
    }
    sort(a + 1, a + n + 1, cmp);
    for (int i = 1;i <= n; i++)
    {
        add(a[i].num, 1);
        ans += sum(a[i].num - 1);
    }
    cout<<ans<<endl;
    return 0;
}

(2)线段树写法

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) >> 1)

const int N = 5e5 + 10;
int n;
struct node { int num, pos; } a[N]; // m存读入的数字,num存数字的位置
struct tree { int sum, tag; } t[N << 2];

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

bool cmp(node x, node y) {
    if (x.num == y.num) return x.pos > y.pos;
    return x.num > y.num;
}

void pushUp(int p) { t[p].sum = t[ls].sum + t[rs].sum; }

void pushDown(int p, int l, int r) {
    if (t[p].tag) {
        t[ls].sum += t[p].tag * (mid - l + 1), t[rs].sum += t[p].tag * (r - mid);
        t[ls].tag += t[p].tag, t[rs].tag += t[p].tag;
        t[p].tag = 0;
    }
}

void modify(int q, int k, int p = 1, int l = 1, int r = n) {
    if (l == r) {
        t[p].sum += (mid - l + 1) * k, t[p].tag += k;
        return;
    }
    pushDown(p, l, r);
    if (q <= mid) modify(q, k, ls, l, mid);
    if (q > mid) modify(q, k, rs, mid + 1, r);
    pushUp(p);
}

int query(int ql, int qr, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) return t[p].sum;
    pushDown(p, l, r); int ans = 0LL;
    if (ql <= mid) ans += query(ql, qr, ls, l, mid);
    if (qr > mid) ans += query(ql, qr, rs, mid + 1, r);
    return ans;
}

signed main() {
    n = read(); for (int i = 1; i <= n; i++) a[i].num = read(), a[i].pos = i;
    sort(a + 1, a + n + 1, cmp);
    int oup = 0LL;
    for (int i = 1; i <= n; i++) {
        oup += query(1, a[i].pos);
        modify(a[i].pos, 1);
    }
    cout<<oup<<endl;
    return 0;
}

(3)归并排序写法

#include <bits/stdc++.h>
using namespace std;

#define int long long

const int N = 5e5 + 10;
int n, a[N], b[N], cnt = 0LL;

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

void msort(int l, int r)
{
    if (l == r) return;
    int mid = (l + r) / 2;
    msort(l, mid), msort(mid + 1, r);
    int p1 = l, p2 = mid + 1, p = l;
    while (p1 <= mid || p2 <= r)
        if (p1 > mid) b[p++] = a[p2++];
        else if (p2 > r) b[p++] = a[p1++];
        else if (a[p1] <= a[p2]) b[p++] = a[p2++];
        else {
            b[p++] = a[p1++];
            cnt += (r - p2 + 1); 
            // 若a[p1] > a[p2],a[p1]大于位于p2右边所有的数,
            // 所以p2及其右边所有数的数量就是该层逆序对的数量
        }
    for (int i = l; i <= r; i++) a[i] = b[i];
}

signed main()
{
    n = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    msort(1, n);
    printf("%lld\n", cnt);
    return 0;
}

2、P1816 忠诚

本题只需要利用线段树维护区间内的最小值,然后在接下来的操作中查询区间最小值即可。

本题只有查询操作没有修改操作,所以不需要pushDown函数。

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) >> 1)

const int N = 1e5 + 10;
int n, m, a[N], t[N << 2];
// 本题的线段树只需要维护区间内的最小值,所以不需要使用结构体数组

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

void pushUp(int p) { t[p] = min(t[ls], t[rs]); }
// 任何一个区间的最小值都是左右儿子最小值中较小的那个

void build(int p = 1, int l = 1, int r = n) {
    if (l == r) { t[p] = a[l]; return; }
    build(ls, l, mid), build(rs, mid + 1, r);
    pushUp(p);
}

int query(int ql, int qr, int p = 1, int l = 1, int  r = n) {
    if (ql <= l && r <= qr) return t[p];
    int temp = 0xfffffff; // 找左右儿子区间的较小值,temp变量初始化为无穷大
    if (ql <= mid) temp = min(temp, query(ql, qr, ls, l, mid));
    if (qr > mid) temp = min(temp, query(ql, qr, rs, mid + 1, r));
    return temp; // 返回左右儿子区间的较小值
}

signed main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    memset(t, 0xfffffff, sizeof(t)); // 由于要维护最小值,t数组初始化为无穷大
    build();
    while (m--) {
        int x = read(), y = read();
        printf("%lld ", query(x, y));
    }
}

3、P3870 [TJOI2009] 开关

可以利用线段树来存区间内开着的灯的数量,进行第一种操作时(把开着的灯关上,关着的灯打开)只需要将t[p].num改成r - l + 1 - t[p].num即可(原本开着的t[p].num盏灯在操作后被关上了,用所有区间内所有灯的数量减去关着的灯的数量就是开着的灯的数量)。

#include <bits/stdc++.h>
using namespace std;

#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) >> 1)

const int N = 1e5 + 10;
int n, m;
struct tree { int num, tag = 0; } t[N << 2];
// num存区间内开着的灯的数量
// tag存是否需要进行开关灯操作,0表示不需要,1表示需要
// 利用异或(^)来改变tag的值

inline int read() {
	int x = 0, f = 1; char c = getchar();
	while (c < '0' || c > '9') {
		if (c == '-') f = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}

void pushUp(int p) { t[p].num = t[ls].num + t[rs].num; }

void pushDown(int p, int l, int r) {
	if (t[p].tag) {
		t[ls].num = mid - l + 1 - t[ls].num, t[rs].num = r - mid - t[rs].num;
        // 区间长度减去关着的灯的数量即为开着的灯的数量
		t[ls].tag ^= 1, t[rs].tag ^= 1, t[p].tag ^= 1;
	}
}

void modify(int ql, int qr, int p = 1, int l = 1, int r = n) {
	if (ql <= l && r <= qr) {
		t[p].num = r - l + 1 - t[p].num, t[p].tag ^= 1;
		return;
	}
	pushDown(p, l, r);
	if (ql <= mid) modify(ql, qr, ls, l, mid);
	if (qr > mid) modify(ql, qr, rs, mid + 1, r);
	pushUp(p);
}

int query(int ql, int qr, int p = 1, int l = 1, int r = n) {
	if (ql <= l && r <= qr) return t[p].num;
	pushDown(p, l, r);
    int ans = 0;
	if (ql <= mid) ans += query(ql, qr, ls, l, mid);
	if (qr > mid) ans += query(ql, qr, rs, mid + 1, r);
	return ans;
}

int main() {
	n = read(), m = read();
	while (m--) {
		int c = read(), a = read(), b = read();
		if (c == 0) modify(a, b);
		if (c == 1) printf("%d\n", query(a, b));
	}
	return 0;
}

4、P1438 无聊的数列

这道题如果直接对原数组进行维护的话,那么在进行修改操作的时候将会非常的麻烦,再加上这道题只需要进行单点查询,所以可以维护一个差分数组(b[i] = a[i] - a[i - 1],其中a为原数组,b为差分数组)。

修改操作:每一次对原数组 a 的 [x,y] 区间加上一个等差数列时,相当于对差分数组 b 的第 x 个数加上等差数列的首项k;如果 x 不等于 y的时候,再对 [x + 1, y] 区间内每一个数加上等差数列公差 d(x 等于 y的时候 x + 1 > y无法修改);如果 y 不等于 n 的时候,再对差分数组第 y + 1 个数减去等差数列最后一项k + (y - x) \times d(y = n 的时候 y + 1 > n,超出线段树维护范围)。

查询操作:查询原数组 a 第 i 项的时候,相当于在查询差分数组 b 的 [1, i] 的和。

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) >> 1)

const int N = 1e5 + 10;
int n, m, a[N], b[N];
struct tree { int sum, tag; } t[N << 2];

inline int read() {
	int x = 0, f = 1; char c = getchar();
	while (c < '0' || c > '9') {
		if (c == '-') f = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}

void pushUp(int p) { t[p].sum = t[ls].sum + t[rs].sum; } // 向上修改

void pushDown(int p, int l, int r) {
	if (t[p].tag) {
		t[ls].sum += (mid - l + 1) * t[p].tag, t[rs].sum += (r - mid) * t[p].tag;
		t[ls].tag += t[p].tag, t[rs].tag += t[p].tag;
		t[p].tag = 0;
	}
} // 下传延迟标记

void build(int p = 1, int l = 1, int r = n) {
	if (l == r) { t[p].sum = b[l]; return; }
	build(ls, l, mid), build(rs, mid + 1, r);
	pushUp(p);
}

void modify(int ql, int qr, int k, int p = 1, int l = 1, int r = n) {
	if (ql <= l && r <= qr) {
		t[p].sum += (r - l + 1) * k, t[p].tag += k;
		return;
	}
	pushDown(p, l, r);
	if (ql <= mid) modify(ql, qr, k, ls, l, mid);
	if (qr > mid) modify(ql, qr, k, rs, mid + 1, r);
	pushUp(p);
}

int query(int ql, int qr, int p = 1, int l = 1, int r = n) {
	if (ql <= l && r <= qr) return t[p].sum;
	pushDown(p, l, r); int ans = 0LL;
	if (ql <= mid) ans += query(ql, qr, ls, l, mid);
	if (qr > mid) ans += query(ql, qr, rs, mid + 1, r);
	return ans;
}

signed main() {
	n = read(), m = read();
	for (int i = 1; i <= n; i++) {
		a[i] = read(), b[i] = a[i] - a[i - 1]; // 差分数组
	}
	build();
	while (m--) {
		int opt = read();
		if (opt == 1) {
			int x = read(), y = read(), k = read(), d = read();
			modify(x, x, k);
			if (x != y) modify(x + 1, y, d);
			if (y != n) modify(y + 1, y + 1, -(k + d * (y - x)));
            // 修改操作
		} else {
			int p = read();
			printf("%lld\n", query(1, p)); // 查询 输出
		}
	}
	return 0;
}

5、P1558 色板游戏

由于最多只有30种颜色,我们可以分别用二进制数来代表颜色种类(第 i 位为1的二进制数代表第 i 种颜色)。

因为计算的是颜色种类,对于第 i 种颜色( 1 \leq i \leq t ), 任何区间的左右的儿子区间只要其中一个存在第 i 种颜色,该区间就会存在第 i 种颜色,所以 pushUp 的时候可以利用位运算或( | )来向上合并。

由于每次修改会直接覆盖掉之前的颜色,pushDown 的时候就直接将子区间覆盖掉就可以了。

#include <bits/stdc++.h>
using namespace std;

#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) >> 1)

const int N = 1e5 + 10;
int L, T, O;
struct tree { int sum, tag; } t[N << 2];

inline int read() {
	int x = 0, f = 1; char c = getchar();
	while (c < '0' || c > '9') {
		if (c == '-') f = -1;
		c = getchar();
	}
	while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
	return x * f;
}

inline void pushUp(int p) { t[p].sum = t[ls].sum | t[rs].sum; }
// 利用或(|)向上合并

inline void pushDown(int p, int l, int r) {
	if (t[p].tag) { // 覆盖掉子区间的颜色
		t[ls].sum = 1 << t[p].tag, t[rs].sum = 1 << t[p].tag;
		t[ls].tag = t[p].tag, t[rs].tag = t[p].tag;
		t[p].tag = 0;
	}
}

inline void build(int p = 1, int l = 1, int r = L) {
	if (l == r) { t[p].sum = 1 << 1; return; }
    // 开始时所有色版的颜色为 1 号色
	build(ls, l, mid), build(rs, mid + 1, r);
	pushUp(p);
}

void modify(int ql, int qr, int k, int p = 1, int l = 1, int r = L) {
	if (ql <= l && r <= qr) {
		t[p].sum = 1 << k, t[p].tag = k; // 覆盖掉原来的颜色
		return;
	}
	pushDown(p, l, r);
	if (ql <= mid) modify(ql, qr, k, ls, l, mid);
	if (qr > mid) modify(ql, qr, k, rs, mid + 1, r);
	pushUp(p);
}

int query(int ql, int qr, int p = 1, int l = 1, int r = L) {
	if (ql <= l && r <= qr) return t[p].sum;
	pushDown(p, l, r);
    int ans = 0;
	if (ql <= mid) ans |= query(ql, qr, ls, l, mid);
	if (qr > mid) ans |= query(ql, qr, rs, mid + 1, r);
    // 利用或(|)合并答案
	return ans;
}

int main() {
	L = read(), T = read(), O = read();
	build();
	while (O--) {
		char c;
        cin >> c;
        int a = read(), b = read();
		if (a > b) swap(a, b);
		if (c == 'C') {
			int k = read();
			modify(a, b, k);
		}
        else if (c == 'P') {
			int cnt = 0, oup = query(a, b);
			while (oup) {
				if (oup % 2 == 1) cnt++;
				oup /= 2;
			} // 计算颜色数量
			printf("%d\n", cnt);
		}
	}
    return 0;
}

6、P4513 小白逛公园

本题难点在于理解如何利用线段树在一段存在负数的区间找到最大的连续区间的和(连续区间的和即为一段连续区间内每一位的数的和,一下简称连续和)。对于每一段的区间,我们可以用线段树存下区间和sum,从左端点出发的最大连续和lm,从右端点出发的最大连续和rm,整个区间最大连续和am。

当递归到最底层的时候,sum = lm = rm = am = a[l],然后再向上传递(具体看代码)。

单点修改所以不需要pushDown函数(具体看Template 3)

对线段树进行访问的时候,函数返回类型为定义的结构体tree,即将整个节点全部信息返回,方便下一步的查找最大连续和。

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) >> 1)

const int N = 5e5 + 10;
int n, m, a[N];
struct tree { int sum, lm, rm, am; } t[N << 2];

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

void pushUp(int p) {
    t[p].sum = t[ls].sum + t[rs].sum;
    t[p].lm = max(t[ls].lm, t[ls].sum + t[rs].lm);
    t[p].rm = max(t[rs].rm, t[rs].sum + t[ls].rm);
    t[p].am = max(max(t[ls].am, t[rs].am), t[ls].rm + t[rs].lm);
}

void build(int p = 1, int l = 1, int r = n) {
    if (l == r) {
        t[p].sum = t[p].lm = t[p].rm = t[p].am = a[l];
        return;
    }
    build(ls, l, mid), build(rs, mid + 1, r);
    pushUp(p); // 往上修改
}

void modify(int q, int k, int p = 1, int l = 1, int r = n) {
    if (l == r) {
        t[p].sum = t[p].lm = t[p].rm = t[p].am = k; // 从最底层开始修改
        return;
    }
    if (q <= mid) modify(q, k, ls, l, mid);
    else modify(q, k, rs, mid + 1, r);
    pushUp(p); // 往上修改
}

tree query(int ql, int qr, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) return t[p]; // 如果[l,r]是[ql,qr]子区间,直接返回t[p]
    if (qr <= mid) {
        return query(ql, qr, ls, l, mid);
        // 如果[ql,qr]位于[l,r]左半区间,那么直接访问[l,r]左半区间
    }
    else {
        if (ql > mid) {
            return query(ql, qr, rs, mid + 1, r);
            // 如果[ql,qr]位于[l,r]右半区间,那么直接访问[l,r]右半区间
        }
        else {
            tree ans, L = query(ql, qr, ls, l, mid), R = query(ql, qr, rs, mid + 1, r);
            ans.sum = L.sum + R.sum;
            ans.lm = max(L.lm, L.sum + R.lm);
            ans.rm = max(R.rm, R.sum + L.rm);
            ans.am = max(max(L.am, R.am), L.rm + R.lm);
            // 在左半区间和右半区间分别进行查找,之后整合结果
            return ans;
        }
    }
}

signed main() {
    n = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    build();
    while (m--) {
        int k = read(), x = read(), y = read();
        if (k == 1) {
            if (x > y) swap(x, y); // 保证 x <= y
            printf("%lld\n", query(x, y).am);
        }
        else modify(x, y);
    }
    return 0;
}

7、P6327 区间加区间 sin 和

(1)和角公式

线段树每个节点存储区间sin和s,区间cos和c(方便后面更新修改)

每一步都根据和角公式\sin (x + v) = \sin(x) \cos(v) + \cos(x) \sin(v)\cos(x + v) = \cos(x) \cos(v) - \sin(x) \sin(v)对线段树进行更新

#include <bits/stdc++.h>
using namespace std;

#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid ((l + r) >> 1)

const int N = 2e5 + 10;
int n, m, v, a[N];
struct tree { double s, c, tag; } t[N << 2];
// s存区间内sin和, c存区间内cos和
// 注意要用double进行存储

inline int read() {
    int x = 0, f = 1; char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}

// 向上修改
void pushUp(int p) { t[p].s = t[ls].s + t[rs].s, t[p].c = t[ls].c + t[rs].c; }

// 下传延迟标记
void pushDown(int p, int l, int r) {
    if (t[p].tag) {
        double lsin = t[ls].s, lcos = t[ls].c, rsin = t[rs].s, rcos = t[rs].c;
        // 下面修改时会改变原来ls, rs存的值,所以要用中间变量临时存储
        t[ls].s = lsin * cos(t[p].tag) + lcos * sin(t[p].tag);
        t[rs].s = rsin * cos(t[p].tag) + rcos * sin(t[p].tag);
        t[ls].c = lcos * cos(t[p].tag) - lsin * sin(t[p].tag);
        t[rs].c = rcos * cos(t[p].tag) - rsin * sin(t[p].tag);
        t[ls].tag += t[p].tag, t[rs].tag += t[p].tag; t[p].tag = 0;
    }
}

void build(int p = 1, int l = 1, int r = n) {
    if (l == r) {
        t[p].s = sin(a[l]), t[p].c = cos(a[l]);
        return;
    }
    build(ls, l, mid), build(rs, mid + 1, r);
    pushUp(p);
}

void modify(int ql, int qr, int k, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) {
        double si = t[p].s, co = t[p].c; // 用中间变量临时存储
        t[p].s = si * cos(k) + co * sin(k);
        t[p].c = co * cos(k) - si * sin(k);
        t[p].tag += k; return;
    }
    pushDown(p, l, r);
    if (ql <= mid) modify(ql, qr, k, ls, l, mid);
    if (qr > mid) modify(ql, qr, k, rs, mid + 1, r);
    pushUp(p);
}

double query(int ql, int qr, int p = 1, int l = 1, int r = n) {
    if (ql <= l && r <= qr) return t[p].s;
    pushDown(p, l, r); double ans = 0.0; // 注意ans要定义成double类型的
    if (ql <= mid) ans += query(ql, qr, ls, l, mid);
    if (qr > mid) ans += query(ql, qr, rs, mid + 1, r);
    return ans;
}

signed main() {
    n = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    build();
    m = read();
    while (m--) {
        int k, l, r, v;
        k = read(), l = read(), r = read();
        if (k == 1) {
            v = read();
            modify(l, r, v);
        }
        if (k == 2) printf("%.1lf\n", query(l, r));
    }
    return 0;
} 
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值