线段树

线段树

1.算法分析

  1. 一般还要开4N的数组
  2. 一般做单点修改、区间查询,加上懒标记后,可以做区间修改、区间查询

可以处理:区间加、区间乘、区间max/min、区间覆盖、区间染色、区间合并、区间开根号、扫描线等问题

1448672-20181017013332469-105750075.png

2.板子

2.1 单点修改+区间查询

// 该板子是求区间和
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 5e5 + 10;
LL dat[N << 2];  // 4倍空间
int n, m, a[N];

// 上传操作
void pushup(int rt) { 
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1];
}

// 建树
void build (int rt, int l, int r) {
    if (l == r) {  // 如果当前到达叶节点,那么赋值
        dat[rt] = a[l];  // 赋值是a[l],表示那个叶节点
        return ;
    }
    int mid = (l + r) >> 1;  
    // 递归建立左右子树
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
    // 上传
    pushup(rt);
}

// 单点修改
void modify (int rt, int l, int r, int x, int y) {
    if (l == x && r == x) {  // 递归到叶节点且叶节点刚好为x节点
        dat[rt] += y;  // 修改
        return;
    }
    int mid = (l + r) >> 1;  
    if (x <= mid) modify(rt << 1, l, mid, x, y);  // 如果在左子树 
    else modify(rt << 1 | 1, mid + 1, r, x, y);  // 不在左子树,比在右子树
    pushup(rt);  // 上传
}

// 区间查询
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];  // 如果当前rt管辖的点能够被[L, R]完全包含,返回
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res += query(rt << 1, l, mid, L, R);  // 如果和左子树有关
    if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);  // 如果可能和右子树有关
    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    build(1, 1, n);  // 建树
    for (int i = 1, op, x, y; i <= m; ++i) {
        scanf("%d%d%d", &op, &x, &y);  
        if (op == 1) modify(1, 1, n, x, y);  // 单点修改a[x]+=y
        else printf("%lld\n", query(1, 1, n, x, y));  // 区间查询,求[x, y]的区间和
    }
    return 0;
}

2.2 区间修改+区间查询

// 该板子是求区间和
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 5e5 + 10;
LL dat[N << 2], lazy[N << 2];
int a[N], n, m;

// 上传标记,每次左右子树建树/区间修改完都需要上传
void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
}

// 建树
void build(int rt, int l, int r) {
    if (l == r) {  // 递归到叶节点
        dat[rt] = a[l];
        lazy[rt] = 0;
        return;
    }
    // 递归建立左右子树
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);  // 上传
}

// 下传,下传标记,同时改变dat数组
void pushdown(int rt, int l, int r) {
    if (lazy[rt]) {  // 如果有标记
        int mid = (l + r) >> 1;
        
        // 把标记给左右子树
        lazy[rt << 1] += lazy[rt];  
        lazy[rt << 1 | 1] += lazy[rt];
        
        // 改变dat
        dat[rt << 1] += (mid - l + 1) * lazy[rt];
        dat[rt << 1 | 1] += (r - mid) * lazy[rt];
        
        // rt标记清空
        lazy[rt] = 0;
    }
    return;
}

// 区间修改: [L, R] += x
void modify(int rt, int l, int r, int L, int R, int x) {
    if (L <= l && r <= R) {  // 如果当前区间被完全包含
        dat[rt] += (r - l + 1) * x;  // 修改当前区间的dat值
        lazy[rt] += x;  // 改变懒标记
        return ;
    }
    
    pushdown(rt, l, r);  // 下传
    // 递归左右子树修改区间
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, x);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, x);
    pushup(rt);  // 上传
    return;
}

// 区间查询:获得[L, R]的区间和
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];  // 如果[l, r]被完全包含于[L, R]
    pushdown(rt, l, r);  // 标记下传
    // 递归加上左右子树
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res += query(rt << 1, l, mid, L, R);
    if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);  // 读入数组
    build(1, 1, n);  // 建树
    for (int i = 1, a, b, x, op; i <= m; ++i) {
        scanf("%d", &op);
        if (op == 1) {
            scanf("%d%d%d", &a, &b, &x);
            modify(1, 1, n, a, b, x);  // 区间修改, [a, b] += x
        }
        else {
            scanf("%d%d", &a, &b);
            printf("%lld\n", query(1, 1, n, a, b));  // 区间查询,查询[a, b]的区间和
        }
    }
    return 0;
}

2.3 区间加乘操作

// 加乘模板
// x点原来的乘、加法标记为:mul1、add1,后来要加上的乘、加法标记为:mul2、add2
// 可以证明先乘后加最优方法
// x的值变为: x.dat => (x.dat * mul2) + (x.r - x.l + 1) * add2;
// x的乘法标记变为: x.mul1 => x.mul1 * mul2
// x的加法标记变为: x.add1 => x.add1 * mul2 + add2
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 1e5 + 10;
LL dat[N << 2], mul[N << 2], add[N << 2];
int n, p, a[N], m;

// 上传,根的值为左子树的值和右子树的值之和
void pushup(int rt) {
    dat[rt] = (dat[rt << 1] + dat[rt << 1 | 1]) % p;
}

// 建树
void build(int rt, int l, int r) {
    if (l == r) {  // 如果是叶子
        dat[rt] = a[l] % p;
        add[rt] = 0;
        mul[rt] = 1;
        return;
    }
    
    // 如果不是叶子,那么乘法标记必须为1,加法标记为0
    mul[rt] = 1;
    add[rt] = 0;
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid), build(rt << 1 | 1, mid + 1, r);  // 建立左子树和右子树
    pushup(rt);
}

// 加乘的结果
void eval(int rt, int l, int r, LL add2, LL mul2) {
    dat[rt] = ((dat[rt] * mul2 % p) + ((r - l + 1) % p) * add2 % p) % p;
    mul[rt] = mul[rt] * mul2 % p;
    add[rt] = (add[rt] * mul2 % p + add2) % p;
}

// 标记下移
void pushdown(int rt, int l, int r) {
    int mid = (l + r) >> 1;
    eval(rt << 1, l, mid, add[rt], mul[rt]), eval(rt << 1 | 1, mid + 1, r, add[rt], mul[rt]);  // 左右子树分别得到根的标记
    add[rt] = 0, mul[rt] = 1;  // 清空根的标记
    return;
}

// 区间修改
void modify(int rt, int l, int r, int L, int R, LL add2, LL mul2) {
    if (L <= l && r <= R) {  // 如果[L, R]在[l, r]内,直接修改
        eval(rt, l, r, add2, mul2);
        return;
    }
    pushdown(rt, l, r);  // 如果不在[l, r]内,那么分裂,首先要把标记下移
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, add2, mul2);  // 修改左子树
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, add2, mul2);  // 修改右子树
    pushup(rt);  // 修改完子树需要把标记上移
    return;
}

// 询问区间和[L, R]
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt] % p;
    pushdown(rt, l, r);  // 如果[L, R]不在[l, r]内,那么需要分裂,首先要把标记下移
    LL res = 0;
    int mid = (l + r) >> 1;
    if (L <= mid) res = query(rt << 1, l, mid, L, R) % p;  // 左子树
    if (mid < R) res = (res + query(rt << 1 | 1, mid + 1, r, L, R) % p) % p;  // 右子树
    return res;
}

int main() {
    cin >> n >> p;  // 输入数字的个数和模数
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);  // 输入数字
    build(1, 1, n);  // 建树
    cin >> m;  // 输入操作数
    for (int i = 1, op, t, g, c; i <= m; ++i) {  // 输入每次的具体操作
        scanf("%d", &op);
        if (op == 1) {  // 区间乘
            scanf("%d%d%d", &t, &g, &c);
            modify(1, 1, n, t, g, 0, c);
        }
        else if (op == 2) {  // 区间加
            scanf("%d%d%d", &t, &g, &c);
            modify(1, 1, n, t, g, c, 1);
        }
        else {  // 询问区间和
            scanf("%d%d", &t, &g);
            cout << query(1, 1, n, t, g) << endl;
        }
    }
    return 0;
}

2.4 区间染色

2.4.1 有限染色问题
/*本题是区间覆盖问题,求指定区间内有多少的颜色数目,因为颜色的数目比较少,因此
可以使用一个int整数来表示所有的颜色数目,而后就是线段树的常规操作*/
#include <bits/stdc++.h>

using namespace std;

int const N = 1e5 + 10;
typedef long long LL;

LL add[N << 2], sum[N << 2];  // add为记录颜色的懒标记,sum为当前区间的颜色

// 向下传递操作
void pushup(int u) {
    sum[u] = sum[u << 1] | sum[u << 1 | 1];  // 当前颜色由子区间颜色得到
}

// 向上传递操作
void pushdown(int u) {
    if (add[u]) {  // 如果当前u节点有颜色的话

        // 给左右子节点标记都赋值
        add[u << 1] = add[u];  
        add[u << 1 | 1] = add[u];

        // 给左右节点的sum赋值,记录颜色
        sum[u << 1] = add[u];
        sum[u << 1 | 1] = add[u];

        // 去掉懒标记
        add[u] = 0;
    }
}

// 建树
void build(int u, int l, int r) {
    add[u] = 0;  // 初始每个节点都没有懒标记
    if (l == r) {  // 如果递归到叶节点
        sum[u] = 1;  // 叶节点的颜色赋值
        return;
    }
    int mid = l + r >> 1;
    build(u << 1, l, mid);  // 建立左右子树
    build(u << 1| 1, mid + 1, r);
    pushup(u);  // 标记上传
}

// 区间赋值操作
void modify(int u, int l, int r, int c, int L, int R) {
    if (L <= l && r <= R) {  // 如果[l, r]完全被包含在要赋值的区间[L, R]的话,那么直接修改
        add[u] = 1 << (c - 1);
        sum[u] = 1 << (c - 1);
        return;
    }
    pushdown(u);  // 下传标记,因为因为标记要分裂
    int mid = l + r >> 1;
    if (L <= mid) modify(u << 1, l, mid, c, L, R);  // 递归修改左右子树
    if (mid < R) modify(u << 1 | 1, mid + 1, r, c, L, R);
    pushup(u);  // 上传操作
}

// 区间查询多少个颜色
LL query(int u, int l, int r, int L, int R) {
    if (L <= l && r <= R) return sum[u];  // 如果[l, r]完全被包含在要赋值的区间[L, R]的话,那么返回
    pushdown(u);  // 标记下移
    int mid = l + r >> 1;
    LL res = 0;

    // 递归查询左右子树
    if (L <= mid) res |= query(u << 1, l, mid, L, R);
    if (mid < R) res |= query(u << 1 | 1, mid + 1, r, L, R);
    return res;
}

int main() {
    int L, T, O, a, b, c; 
    cin >> L >> T >> O;  // 读入节点数、颜色总数、操作数
    build(1, 1, L);
    while (O--) {
        char op[2];
        scanf("%s", op);  // 读入操作类型
        if (op[0] == 'P') {
            scanf("%d %d", &a, &b);
            if (a > b) swap(a, b);  // 保证a要比b小
            LL ans = query(1, 1, L, a, b);  // 查询a到b的颜色总数,颜色总数用一个int型数表示
            LL res = 0;  
            while (ans) {  // 记录这个int型数有多少个1
                if (ans & 1) res++;
                ans >>= 1;
            }
            printf("%lld\n", res);
        }
        else {
            scanf("%d%d%d", &a, &b, &c);  // 读入[a, b]和修改为的值
            if (a > b) swap(a, b);
            modify(1, 1, L, c, a, b);  // 修改操作
        }
    }
    return 0;
}
2.4.2 一般染色问题
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <set>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

int const N = 2e4 + 10;
LL lazy[N << 2];
int a[N], n, m;
vector<int> v;
PII query[N / 2];

void build (int rt, int l, int r) {
    if (l == r) {  // 如果当前到达叶节点,那么赋值
        lazy[rt] = 0;  // 赋值是a[l],表示那个叶节点
        return ;
    }
    int mid = (l + r) >> 1;  
    // 递归建立左右子树
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
}

// 下传,下传标记,同时改变dat数组
void pushdown(int rt, int l, int r) {
    if (lazy[rt]) {  // 如果有标记
        int mid = (l + r) >> 1;

        // 把标记给左右子树
        lazy[rt << 1] = lazy[rt];
        lazy[rt << 1 | 1] = lazy[rt];

        // rt标记清空
        lazy[rt] = 0;
    }
    return;
}

// 单点查询:a[l]
LL Query(int rt, int l, int r, int x) {
    if (l == r) return lazy[rt];  // 如果[l, r]被完全包含于[L, R]
    pushdown(rt, l, r);                     // 标记下传
    // 递归加上左右子树
    int mid = (l + r) >> 1;
    if (x <= mid) return Query(rt << 1, l, mid, x);
    else return Query(rt << 1 | 1, mid + 1, r, x);
}

// 区间修改: [L, R] = x
void modify(int rt, int l, int r, int L, int R, int x) {
    if (L <= l && r <= R) {  // 如果当前区间被完全包含
        lazy[rt] = x;        // 改变懒标记
        return;
    }

    pushdown(rt, l, r);  // 下传
    // 递归左右子树修改区间
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, x);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, x);
    return;
}

// 离散化
int getid(int x) { return lower_bound(v.begin(), v.end(), x) - v.begin() + 1; }

int main() {
    int T;
    cin >> T;
    while (T--) {
        v.clear();
        set<int> s;
        cin >> n;

        // 离散化
        for (int i = 1; i <= n; ++i) {
            scanf("%d%d", &query[i].first, &query[i].second);
            v.push_back(query[i].first), v.push_back(query[i].second);
        }
        sort(v.begin(), v.end());
        v.erase((v.begin(), v.end()), v.end());
        int len = v.size();

        // 建树,相当于memset,但是节约了时间
        build(1, 1, len);
        
        // 离散化赋值回去
        for (int i = 1; i <= n; ++i) {
            query[i].first = getid(query[i].first);
            query[i].second = getid(query[i].second);
        }

        // 区间赋值
        for (int i = 1; i <= n; ++i) modify(1, 1, len, query[i].first, query[i].second, i);

        // 计算每个点的颜色
        for (int i = 1; i <= len; ++i) {
            int tmp = Query(1, 1, len, i);
            if (tmp) s.insert(tmp);
        }
        printf("%d\n", s.size());
    }
    return 0;
}

2.5 区间开根号

// 由于数字最大只到long long,因此最多开发8次,那么直接暴力,递归到叶子节点,暴力修改后pushup,然后再普通查询
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 1e5 + 10;
LL dat[N << 2], a[N];
int n, m, kase = 1;

// 上传标记,每次左右子树建树/区间修改完都需要上传
void pushup(int rt) { dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; }

// 建树
void build(int rt, int l, int r) {
    if (l == r) {  // 递归到叶节点
        dat[rt] = a[l];
        return;
    }
    // 递归建立左右子树
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);  // 上传
}

// 区间修改: [L, R] 开方
void modify(int rt, int l, int r, int L, int R) {
    if (dat[rt] == r - l + 1) return;  // 如果这个区间全部都为1
    if (l == r) {  // 如果递归到叶子节点,那么直接修改
        dat[rt] = (int)sqrt((double)dat[rt]);
        return;
    }

    // 递归左右子树修改区间
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R);
    pushup(rt);  // 上传
    return;
}

// 区间查询:获得[L, R]的区间和
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];  // 如果[l, r]被完全包含于[L, R]
    // 递归加上左右子树
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res += query(rt << 1, l, mid, L, R);
    if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

int main() {
    while (scanf("%d", &n) != EOF) {
        printf("Case #%d:\n", kase++);
        for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);  // 读入数组
        build(1, 1, n);
        cin >> m;
        for (int i = 1, a, b, op; i <= m; ++i) {
            scanf("%d%d%d", &op, &a, &b);
            if (a > b) swap(a, b);
            if (op == 0)
                modify(1, 1, n, a, b);  // 区间修改, [a, b]开方
            else
                printf("%lld\n", query(1, 1, n, a, b));  // 区间查询,查询[a, b]的区间和
        }
        puts("");
    }
    return 0;
}

2.6 区间合并

2.6.1 最大连续子段和
// 求最大连续的子段和
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 3;

int n, m;
typedef long long LL;
LL a[N];
struct node {
    int l, r;
    LL lmax, rmax, sum, tmax;  // lmax维护从左端点开始的最大子段和,rmax维护从右端点开始的最大子段和,sum维护区间和,tmax维护整个区间的最大子段和
} tr[N * 4];

void pushup(node &u, node &a, node &b) {
    u.lmax = max(a.lmax, a.sum + b.lmax);
    u.rmax = max(b.rmax, a.rmax + b.sum);
    u.sum = a.sum + b.sum;
    u.tmax = max(a.tmax, max(b.tmax, a.rmax + b.lmax));
}

void build(int u, int l, int r) {
    tr[u].l = l;
    tr[u].r = r;
    if (l == r) {
        tr[u].lmax = tr[u].rmax = tr[u].sum = tr[u].tmax = a[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(u * 2, l, mid);
    build(u * 2 + 1, mid + 1, r);
    pushup(tr[u], tr[u * 2], tr[u * 2 + 1]);
}

// 修改a[x] = c
void modify(int u, int x, LL c) {
    if (tr[u].l == tr[u].r) {
        tr[u].lmax = tr[u].rmax = tr[u].sum = tr[u].tmax = c;
        return;
    }
    int mid = (tr[u].l + tr[u].r) / 2;
    if (x <= mid)
        modify(u * 2, x, c);
    else
        modify(u * 2 + 1, x, c);
    pushup(tr[u], tr[u * 2], tr[u * 2 + 1]);
}

// 查询[l, r]的最大子段和
node query(int u, int l, int r) {
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    int mid = (tr[u].l + tr[u].r) / 2;
    if (r <= mid)  return query(u * 2, l, r);
    else {
        if (l > mid) return query(u * 2 + 1, l, r);
        else {
            auto left = query(u * 2, l, r);
            auto right = query(u * 2 + 1, l, r);
            node kn;
            pushup(kn, left, right);
            return kn;
        }
    }
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
    build(1, 1, n);
    for (int i = 0; i < m; i++) {
        int op, x, y;
        scanf("%d%d%lld", &op, &x, &y);
        if (op == 1) {  // query
            if (x > y) swap(x, y);
            printf("%lld\n", query(1, x, y).tmax);  // 询问区间最大子段和
        } else {  
            modify(1, x, y);  // 将a[x] = y
        }
    }
    return 0;
}
2.6.2 最大连续区间
// 求最大连续区间长度
#include <bits/stdc++.h>

using namespace std;

int const N = 5e4 + 10;
 // lsum数组记录区间左端点开始的最大连续个数,rsum数组记录区间右端点开始往左的最大的连续个数, sum数组表示该区间最大的连续点的个数。
int sum[N << 2], lsum[N << 2], rsum[N << 2]; 
int a[N];
int n, m, x, tot;

void pushup(int rt, int len) {
    lsum[rt] = lsum[rt << 1];
    rsum[rt] = rsum[rt << 1 | 1];
    if (lsum[rt << 1] == (len - (len >> 1)))
        lsum[rt] = lsum[rt << 1] + lsum[rt << 1 | 1];
    if (rsum[rt << 1 | 1] == (len >> 1))
        rsum[rt] = rsum[rt << 1] + rsum[rt << 1 | 1];
    sum[rt] = max(rsum[1 << 1] + lsum[1 << 1 | 1], max(sum[rt << 1], sum[rt << 1 | 1]));
}

void build(int rt, int l, int r) {
    if (l == r) {
        sum[rt] = lsum[rt] = rsum[rt] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt, r - l + 1);
}

// a[pos] = c
void modify(int rt, int l, int r, int pos, int c) {
    if (l == r) {
        sum[rt] = rsum[rt] = lsum[rt] = c;
        return;
    }
    int mid = (l + r) >> 1;
    if (pos <= mid)
        modify(rt << 1, l, mid, pos, c);
    else
        modify(rt << 1 | 1, mid + 1, r, pos, c);
    pushup(rt, r - l + 1);
}

// 询问包含x的最长连续区间长度
int query(int rt, int l, int r, int pos) {
    if (l == r) return sum[rt];
    int mid = (l + r) >> 1;
    if (pos <= mid) {
        if (pos + rsum[rt << 1] > mid)
            return rsum[rt << 1] + lsum[rt << 1 | 1];
        else
            return query(rt << 1, l, mid, pos);
    } else {
        if (mid + lsum[rt << 1 | 1] >= pos)
            return lsum[rt << 1 | 1] + rsum[rt << 1];
        else
            return query(rt << 1 | 1, mid + 1, r, pos);
    }
}

int main() {
    char op[10];
    while (scanf("%d%d", &n, &m) != EOF) {
        build(1, 1, n);
        tot = 0;
        while (m--) {
            scanf("%s", op);
            if (op[0] == 'Q') {
                scanf("%d", &x);
                printf("%d\n", query(1, 1, n, x));  // 询问包含x的最长连续区间长度
            } else if (op[0] == 'D') {
                scanf("%d", &x);
                a[++tot] = x;
                modify(1, 1, n, x, 0);  // a[x] = 0
            } else {
                x = a[tot--];
                modify(1, 1, n, x, 1);  // a[x] = 1
            }
        }
    }
    return 0;
}

2.7 区间每个数字的平方和、立方和

/*
	对于一个区间有4个操作:
	1.将a~b都加上c
	2.将a~b都乘上c
	3.将a~b都变成c
	4.查询a~b的每个数的p次方的和。(p=1,2,3)
*/
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 5;
typedef long long LL;
const int p = 10007;
int n, m, r, t;

int a[N], sum1[N * 4], sum2[N * 4], sum3[N * 4], lazy_mul[N * 4], lazy_add[N * 4];  // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记

void solve(int rt, int len, int a, int b) {  // a为add b为mul
    // 修改乘法和加法标记
    lazy_mul[rt] = 1ll * lazy_mul[rt] * b % p;
    lazy_add[rt] = 1ll * lazy_add[rt] * b % p;
    lazy_add[rt] = ((lazy_add[rt] + a) % p + p) % p;

    // 先维护乘法标记:x^3=>(bx)^3, x^2=>(bx)^2
    if (b != 1) {  //先乘后加
        sum1[rt] = 1ll * sum1[rt] * b % p;
        sum2[rt] = (1ll * sum2[rt] * b % p) * b % p;
        sum3[rt] = ((1ll * sum3[rt] * b % p) * b % p) * b % p;
    }

    // 再维护加法标记:(x+a)^3=x^3+3x^2*a+3xa^2+a^3, (x+a)^2=x^2+2xa+a^2
    if (a != 0) {
        int a2 = 1ll * a * a % p, a3 = 1ll * a2 * a % p;
        sum3[rt] = ((sum3[rt] + (LL)len * a3 % p) + p) % p;
        sum3[rt] = ((sum3[rt] + 3ll * (LL)sum2[rt] % p * a % p) + p) % p;
        sum3[rt] = ((sum3[rt] + 3ll * (LL)sum1[rt] % p * a2 % p) + p) % p;
        sum2[rt] = ((sum2[rt] + 2ll * (LL)sum1[rt] % p * a % p) + p) % p;
        sum2[rt] = ((sum2[rt] + (LL)len * a2 % p) + p) % p;
        sum1[rt] = ((sum1[rt] + (LL)len * a % p) + p) % p;
    }
}

void pushup(int rt) {
    sum1[rt] = (sum1[rt << 1] + sum1[rt << 1 | 1]) % p;
    sum2[rt] = (sum2[rt << 1] + sum2[rt << 1 | 1]) % p;
    sum3[rt] = (sum3[rt << 1] + sum3[rt << 1 | 1]) % p;
}

// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r) {
    lazy_add[rt] = 0;
    lazy_mul[rt] = 1;
    if (l == r) {
        int temp = a[l];
        sum1[rt] = temp;
        sum2[rt] = (1ll * sum1[rt] * sum1[rt]) % p;
        sum3[rt] = (1ll * sum1[rt] * sum2[rt]) % p;
        return;
    }
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);
}

// 下传
void pushdown(int rt, int l, int r) {
    int mid = (l + r) >> 1;
    solve(rt << 1, mid - l + 1, lazy_add[rt], lazy_mul[rt]);
    solve(rt << 1 | 1, r - mid, lazy_add[rt], lazy_mul[rt]);
    lazy_add[rt] = 0;
    lazy_mul[rt] = 1;
}

// L为需要修改的左区间,R为需要修改的右区间
void modify(int rt, int l, int r, int L, int R, int a, int b) {
    if (L <= l && r <= R) {
        solve(rt, r - l + 1, a, b);
        return;
    }
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, a, b);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, a, b);
    pushup(rt);
}

// L为需要查询的左区间,R为查询的右区间
int query(int rt, int l, int r, int L, int R, int cnt) {
    if (L <= l && r <= R) {
        if (cnt == 1)
            return sum1[rt];
        else if (cnt == 2)
            return sum2[rt];
        else
            return sum3[rt];
    }
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    int ans = 0;
    if (L <= mid) ans += query(rt << 1, l, mid, L, R, cnt), ans %= p;
    if (mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R, cnt), ans %= p;
    pushup(rt);
    return ans;
}

int main() {
    while (scanf("%d%d", &n, &m) != EOF) {
        if (!n && !m) break;
        build(1, 1, n);
        // m次询问
        for (int i = 1, op, x, y, z; i <= m; i++) {
            scanf("%d%d%d%d", &op, &x, &y, &z);
            if (op == 1)
                modify(1, 1, n, x, y, z, 1);  // [x, y] += c
            else if (op == 2)
                modify(1, 1, n, x, y, 0, z);  // [x, y] *= c
            else if (op == 3)
                modify(1, 1, n, x, y, z, 0);  // [x, y] = c
            else
                printf("%d\n", query(1, 1, n, x, y, z));  // [x, y] 每个数字的z次方的和
        }
    }
    return 0;
}

2.8 扫描线求矩形面积并

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int n;
struct Segment {
    double x, y1, y2;
    int k;
    bool operator<(const Segment &t) const { return x < t.x; }
} seg[N * 2];
int cnt[N << 3];  // cnt[u]维护u的管理区间是否全部都被覆盖
double len[N << 3];  // len[u]维护u的管理区间内覆盖的长度

vector<double> ys;

int find(double y) { return lower_bound(ys.begin(), ys.end(), y) - ys.begin(); }

void pushup(int u, int l, int r) {
    if (cnt[u])  // 如果这段都被覆盖,那么有效的长度就是这段长度
        len[u] = ys[r + 1] - ys[l];
    else if (l != r)  // 如果这段没有完全覆盖,那么就是左子区间被覆盖长度+右子区间被覆盖长度
        len[u] = len[u << 1] + len[u << 1 | 1];
    else  // 要不然就是0
        len[u] = 0;
}

void build(int u, int l, int r) {
    len[u] = cnt[u] = 0;
    if (l != r) {
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    }
}

void modify(int u, int l, int r, int L, int R, int k) {
    if (L <= l && r <= R) {
        cnt[u] += k;
        pushup(u, l, r);
    } else {
        int mid = l + r >> 1;
        if (L <= mid) modify(u << 1, l, mid, L, R, k);
        if (mid < R) modify(u << 1 | 1, mid + 1, r, L, R, k);
        pushup(u, l, r);
    }
}

int main() {
    int T = 1;
    while (scanf("%d", &n) != EOF) {
        if (!n) break;
        ys.clear();
        for (int i = 0, j = 0; i < n; i++) {
            double x1, y1, x2, y2;
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            seg[j++] = {x1, y1, y2, 1};  // 矩形的左边界+1
            seg[j++] = {x2, y1, y2, -1};  // 右边界减1
            ys.push_back(y1), ys.push_back(y2);  // y离散化
        }

        // 排序去重
        sort(ys.begin(), ys.end());
        ys.erase(unique(ys.begin(), ys.end()), ys.end());

        build(1, 0, ys.size() - 2);

        sort(seg, seg + n * 2);

        double res = 0;
        for (int i = 0; i < n * 2; i++) {
            if (i > 0) res += len[1] * (seg[i].x - seg[i - 1].x);  // 每次都是计算全部长度的len,所以只需要len[1],矩形面积就是len[1]为宽,乘上(x2 - x1)为长
            modify(1, 0, ys.size() - 2, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);  // 把这段都加上k
        }

        printf("Test case #%d\n", T++);
        printf("Total explored area: %.2lf\n\n", res);
    }
    return 0;
}

3. 例题

3.1 常规操作题

luogu P1047 校门外的树
题意: 有一个数轴,长度为l+1,从0~l上每个点都种树。现在有m个操作,每个操作输入a和b,表示要把[a, b]上的树砍掉,问m次操作后,数轴上还剩下多少棵树?
题解: 只需要改区间修改+区间查询的板子即可,当砍掉[a,b]上的树时,就算把[a, b]赋值为0,最后统计还剩多少棵树,就算计算[1,n]的区间求和。
代码:

// 该板子是求区间和
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 5e5 + 10;
LL dat[N << 2], lazy[N << 2];
int a[N], n, m;

// 上传标记,每次左右子树建树/区间修改完都需要上传
void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
}

// 建树
void build(int rt, int l, int r) {
    if (l == r) {  // 递归到叶节点
        dat[rt] = a[l];
        lazy[rt] = 1;
        return;
    }
    lazy[rt] = 1;
    // 递归建立左右子树
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);  // 上传
}

// 下传,下传标记,同时改变dat数组
void pushdown(int rt, int l, int r) {
    if (lazy[rt] == 0) {  // 如果有标记
        
        // 把标记给左右子树
        lazy[rt << 1] = 0;  
        lazy[rt << 1 | 1] = 0;
        
        // 改变dat
        dat[rt << 1] = 0 ;
        dat[rt << 1 | 1] = 0;
        
        // rt标记清空
        lazy[rt] = 1;
    }
    return;
}

// 区间修改: [L, R] += x
void modify(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) {  // 如果当前区间被完全包含
        dat[rt] = 0;  // 修改当前区间的dat值
        lazy[rt] = 0;  // 改变懒标记
        return ;
    }
    
    if (lazy[rt] == 0) pushdown(rt, l, r);  // 下传
    // 递归左右子树修改区间
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R);
    pushup(rt);  // 上传
    return;
}

// 区间查询:获得[L, R]的区间和
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];  // 如果[l, r]被完全包含于[L, R]
    if (lazy[rt] == 0) pushdown(rt, l, r);  // 标记下传
    // 递归加上左右子树
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res += query(rt << 1, l, mid, L, R);
    if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

int main() {
    cin >> n >> m;
    n ++;
    for (int i = 1; i <= n; ++i) a[i] = 1;
    build(1, 1, n);  // 建树
    // cout << query(1, 1, n, 1, n) << endl;
    for (int i = 1, a, b; i <= m; ++i) {
        scanf("%d%d", &a, &b);
        a++, b++;
        modify(1, 1, n, a, b);
    }
    cout << query(1, 1, n, 1, n) << endl;
    return 0;
}

luogu P5057 [CQOI2006]简单题
题意: 有一个 n 个元素的数组,每个元素初始均为 0。有 m 条指令,要么让其中一段连续序列数字反转——0 变 1,1 变 0(操作 1),要么询问某个元素的值(操作 2)。 1 ≤ n ≤ 105, 1 ≤ m ≤ 5 × 105
题解: 线段树维护,每次给定反转区间[a, b],那么把[a, b]区间中每个数字加1,而后每次询问x的时候,只需要query(1,1,n,x,x),而后判断这个值是奇数还是偶数,奇数输出1,偶数输出0即可
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

int const N = 1e5 + 10;
LL dat[N << 2], lazy[N << 2];
int n, m;

// 上传标记,每次左右子树建树/区间修改完都需要上传
void pushup(int rt) {
    dat[rt] = dat[rt << 1] + dat[rt << 1 | 1]; 
}

// 建树
void build(int rt, int l, int r) {
    if (l == r) {  // 递归到叶节点
        dat[rt] = 0;
        lazy[rt] = 0;
        return;
    }
    // 递归建立左右子树
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);  
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);  // 上传
}

// 下传,下传标记,同时改变dat数组
void pushdown(int rt, int l, int r) {
    if (lazy[rt]) {  // 如果有标记
        int mid = (l + r) >> 1;
        
        // 把标记给左右子树
        lazy[rt << 1] += lazy[rt];  
        lazy[rt << 1 | 1] += lazy[rt];
        
        // 改变dat
        dat[rt << 1] += (mid - l + 1) * lazy[rt];
        dat[rt << 1 | 1] += (r - mid) * lazy[rt];
        
        // rt标记清空
        lazy[rt] = 0;
    }
    return;
}

// 区间修改: [L, R] += x
void modify(int rt, int l, int r, int L, int R, int x) {
    if (L <= l && r <= R) {  // 如果当前区间被完全包含
        dat[rt] += (r - l + 1) * x;  // 修改当前区间的dat值
        lazy[rt] += x;  // 改变懒标记
        return ;
    }
    
    pushdown(rt, l, r);  // 下传
    // 递归左右子树修改区间
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, x);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, x);
    pushup(rt);  // 上传
    return;
}

// 区间查询:获得[L, R]的区间和
LL query(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) return dat[rt];  // 如果[l, r]被完全包含于[L, R]
    pushdown(rt, l, r);  // 标记下传
    // 递归加上左右子树
    int mid = (l + r) >> 1;
    LL res = 0;
    if (L <= mid) res += query(rt << 1, l, mid, L, R);
    if (mid < R) res += query(rt << 1 | 1, mid + 1, r, L, R);
    return res;
}

int main() {
    cin >> n >> m;
    build(1, 1, n);  // 建树
    for (int i = 1, a, b, x, op; i <= m; ++i) {
        scanf("%d", &op);
        if (op == 1) {
            scanf("%d%d", &a, &b);
            modify(1, 1, n, a, b, 1);  // 区间修改, [a, b] += 1
        }
        else {
            scanf("%d", &a);
            printf("%lld\n", (query(1, 1, n, a, a) & 1) == 1);  // 区间查询,查询[a, b]的区间和
        }
    }
    return 0;
}

luogu P4588 [TJOI2018]数学计算
题意: 小豆现在有一个数x,初始值为1.小豆有Q次操作,操作有两种类型:
1 m: x = x * m, 输出x%mod;
2 pos:x= x = x / 第pos次操作所乘的数(保证第pos次操作一定为类型1,对于每一个类型1的操作至多会被除一次)输出x % mod;Q <= 105
题解: 使用线段树维护1~Q这Q个数字的区间乘,如果当前是1类型操作,那么进行单点修改modify(1, 1, n, i, x);如果是2类型操作,那么进行单点修改modify(1, 1, n, pos, 1); 每次输出都是所有的成绩, 即dat[1];
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 1e5 + 10;
LL dat[N << 2];
int n, p, t;

void pushup(int rt) {
    dat[rt] = dat[rt << 1] * dat[rt << 1 | 1] % p;
}

void build(int rt, int l, int r) {
    if (l == r) {
        dat[rt] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid), build(rt << 1 | 1, mid + 1, r);
    pushup(rt);
}

void modify(int rt, int l, int r, int x, int y) {
    if (l == r && l == x) {
        dat[rt] = y;
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid) modify(rt << 1, l, mid, x, y);
    else modify(rt << 1 | 1, mid + 1, r, x, y);
    pushup(rt);
}

int main() {
    cin >> t;
    while (t--) {
        cin >> n >> p;
        for (int i = 1; i <= n * 4; ++i) dat[i] = 0;
        build(1, 1, n);
        for (int i = 1, op, x; i <= n; ++i) {
            scanf("%d%d", &op, &x);
            if (op == 1) modify(1, 1, n, i, x % p);
            else modify(1, 1, n, x, 1);
            printf("%lld\n", dat[1] % p);
        }
    }
    return 0;
}

icpc2019上海区域赛 F.A Simple Problem On A Tree

题意: 给定一棵树,维护4个操作:

1 u v w:将u->v路径上的点全部赋值为w

2 u v w:将u->v路径上的点全部加上w

3 u v w:将u->v路径上的点全部乘上w

4 u v:询问u->v路径上的点的立方和

题解: 本题考的就是维护4个操作:区间赋值,区间加,区间乘,区间立方和。难点在于维护立方和,加乘操作就是先乘后加。先树剖,然后dfs序建线段树,然后处理即可。

代码:

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

const int N = 1e5+5;
typedef long long LL;
const int p = 1e9 + 7;
int tot, num;
int n, m, r,t,cases=0;

int w[N], a[N], sum1[N * 4], sum2[N * 4], sum3[N * 4], lazy_mul[N * 4], lazy_add[N * 4]; // w[i]=j表示时间戳为i的点的值为j,a[]输入每个节点的值,dat线段树每个点权值,lazy线段树每个点的懒标记
//int h[N], e[N * 2], ne[N * 2], idx;   // 邻接表数组
int dep[N], dfn[N], wson[N], sz[N], top[N], fa[N]; //  dep深度   dfn搜索序 wson重儿子 size子树大小 top链头  fa父节点
vector<int> mp[N];

// 得到sz, fa, dep, wson数组
void dfs1(int u) {
    dep[u] = dep[fa[u]]+1; 
    sz[u] = 1; 
    for(int i = 0; i<mp[u].size(); i++) {
        int j=mp[u][i]; 
        if(j == fa[u]) continue; 
        fa[j] = u; 
        dfs1(j); 
        sz[u] += sz[j]; 
        if(sz[j] > sz[wson[u]]) wson[u] = j;  // 这里要注意根节点不能设为0,否则根节点的最重链无法更新,始终为0
    }
}

// 得到dfn, top数组 
void dfs2(int u, int nowtop) {
    dfn[u] = ++num; 
    w[num] = a[u]; 
    //以搜索序重排权值
    top[u] = nowtop; 
    if(wson[u]) dfs2(wson[u], nowtop);   // 先搜索重儿子
    for(int i = 0; i<mp[u].size(); i++) {// 然后搜索轻儿子
        int y=mp[u][i]; 
        if(y ==fa[u]||y == wson[u]) continue; 
        dfs2(y, y); 
    }
}

void solve(int rt,int len,int a,int b){   //a为add b为mul
    // 修改乘法和加法标记
    lazy_mul[rt] = 1ll*lazy_mul[rt] * b % p;
    lazy_add[rt] = 1ll*lazy_add[rt] * b % p;
    lazy_add[rt] = ((lazy_add[rt] + a) % p + p) % p;

    // 先维护乘法标记:x^3=>(bx)^3, x^2=>(bx)^2
    if(b!=1){   //先乘后加
        sum1[rt] = 1ll*sum1[rt] * b % p;
        sum2[rt] = (1ll*sum2[rt] * b % p) * b % p;
        sum3[rt] = ((1ll*sum3[rt] * b % p) * b % p) * b % p;
    }

    // 再维护加法标记:(x+a)^3=x^3+3x^2*a+3xa^2+a^3, (x+a)^2=x^2+2xa+a^2
    if(a!=0){
        int a2 = 1ll*a * a % p, a3 = 1ll*a2 * a % p;
        sum3[rt] = ((sum3[rt] + (LL)len * a3 % p) + p) % p;
        sum3[rt] = ((sum3[rt] + 3ll * (LL)sum2[rt] % p * a % p) + p) % p;
        sum3[rt] = ((sum3[rt] + 3ll * (LL)sum1[rt] % p * a2 % p) + p) % p;
        sum2[rt] = ((sum2[rt] + 2ll * (LL)sum1[rt] % p * a % p) + p) % p;
        sum2[rt] = ((sum2[rt] + (LL)len * a2 % p) + p) % p;
        sum1[rt] = ((sum1[rt] + (LL)len * a % p) + p) % p;
    }
}

void pushup(int rt) {
    sum1[rt] = (sum1[rt << 1] + sum1[rt << 1 | 1]) % p;
    sum2[rt] = (sum2[rt << 1] + sum2[rt << 1 | 1]) % p;
    sum3[rt] = (sum3[rt << 1] + sum3[rt << 1 | 1]) % p;
}   

// 建线段树,rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界
void build(int rt, int l, int r) {
    lazy_add[rt] = 0;
    lazy_mul[rt] = 1;
    if(l==r) {
        int temp = w[l];
        sum1[rt] = temp;
        sum2[rt] = (1ll*sum1[rt] * sum1[rt]) % p;
        sum3[rt] = (1ll*sum1[rt] * sum2[rt]) % p;
        return ; 
    }
    int mid=(l + r)>>1; 
    build(rt << 1, l, mid); 
    build(rt << 1 | 1, mid+1, r); 
    pushup(rt);
}

// 下传
void pushdown(int rt, int l, int r) {
    int mid = (l + r) >> 1;
    solve(rt << 1, mid - l + 1, lazy_add[rt], lazy_mul[rt]);
    solve(rt << 1 | 1, r - mid, lazy_add[rt], lazy_mul[rt]);
    lazy_add[rt] = 0;
    lazy_mul[rt] = 1;
}

// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, L为需要修改的左区间,R为需要修改的右区间
void modify(int rt, int l, int r, int L, int R, int a,int b) {
    if(L <= l && r <= R) {
        solve(rt, r - l + 1, a, b);
        return ; 
    } 
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    if(L <= mid) modify(rt << 1, l, mid, L, R, a,b); 
    if(mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, a,b); 
    pushup(rt);
}

// rt为根,l为rt点管辖的左边界, r为rt点管辖的有边界, L为需要查询的左区间,R为查询的右区间
int query(int rt, int l, int r, int L, int R) {
    if(L <= l && r <= R) {
        return sum3[rt]; 
    }
    pushdown(rt, l, r); 
    int mid = (l + r)>>1; 
    int ans = 0; 
    if(L <= mid) ans += query(rt << 1, l, mid, L, R), ans %= p; 
    if(mid < R) ans += query(rt << 1 | 1, mid + 1, r, L, R), ans %= p;
    pushup(rt);
    return ans; 
}

// 求树从 x 到 y 结点最短路径上所有节点的值之和
int path_query_Tree(int x, int y) {   
    //两点间的修改
    int ans = 0; 
    while(top[x] != top[y]) {// 把x点和y点整到一条重链上
        if(dep[top[x]] < dep[top[y]]) swap(x, y);   // 让x点为深的那个点
        ans += query(1, 1, n, dfn[top[x]], dfn[x]);   
        ans %= p; 
        x = fa[top[x]];  // x每次跳一条链
    }
    if(dep[x] > dep[y]) swap(x, y);   // 让x成为深度更浅的那个点
    ans += query(1, 1, n, dfn[x], dfn[y]); 
    return ans % p; 
}

// 树上修改 a为add ,b为mul
void path_modify_Tree(int x, int y, int a,int b) {
    //树上两点距离 
    while(top[x] != top[y]) { // 把x点和y点整到一条重链上
        if(dep[top[x]] < dep[top[y]]) swap(x, y); // 让x成为对应的头部深度更大的那个点
        modify(1, 1, n, dfn[top[x]], dfn[x], a,b); // 累加x的所有子树和
        x = fa[top[x]]; // x跳到原来x的头部的父节点
    }
    if(dep[x] > dep[y]) swap(x, y);  // 让x成为深度更浅的那个点
    modify(1, 1, n, dfn[x], dfn[y], a,b); 
}

int main() {
    scanf("%d", &t);
    while(t--){
        scanf("%d", &n);
        cases++;
        // 读入边,建树
        for(int i=1; i<=n; ++i) mp[i].clear(),wson[i]=0;
        //memset(h,  -1,  sizeof h);
        num = 0;
        for(int i=1, x, y; i<n; i++) {
            scanf("%d%d", &x, &y);
            mp[x].push_back(y);
            mp[y].push_back(x); 
        }

        for(int i=1; i<=n; i++) scanf("%d", &a[i]); // 读入每个点的权值
        // 两次dfs把树按照重链剖分
        dfs1(1);   // 得到sz, fa, dep, wson数组
        dfs2(1, 1);   // 得到dfn, top数组
        build(1, 1, n); 
        scanf("%d", &m);  
        printf("Case #%d:\n", cases);
        // m次询问
        for(int i=1, op, x, y, z; i<=m; i++) {
            scanf("%d", &op); 
            if(op == 1) {
                scanf("%d%d%d", &x, &y, &z); 
                path_modify_Tree(x, y, z,0); // 将树从 x到 y 结点最短路径上所有节点的值赋值为z
            }
            else if(op == 2) {
                scanf("%d%d%d", &x, &y, &z); 
                path_modify_Tree(x, y, z,1); // 将树从 x到 y 结点最短路径上所有节点的值加上z
            }
            else if(op == 3) {
                scanf("%d%d%d", &x, &y, &z); 
                path_modify_Tree(x, y, 0,z); // 将树从 x到 y 结点最短路径上所有节点的值乘上z
            }
            else {
                scanf("%d%d", &x,&y); 
                printf("%d\n", path_query_Tree(x,y)); 
            }
        }
    } 

    return 0;
}

ZOJ 1610 Count the Colors

题意: 在0-8000长的线段里面,按先后次序依次覆盖颜色,求最后每种颜色有多少段?

题解: 首先是区间染色,然后计算每个点的颜色,最后问题就转换为区间划分问题,按照双指针的模板操作即可。

**代码: **

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

int const N = 8010;
LL lazy[N << 2];
map<int, int> s;
int a[N], n, m;

void build(int rt, int l, int r) {
    if (l == r) {      // 如果当前到达叶节点,那么赋值
        lazy[rt] = 0;  // 赋值是a[l],表示那个叶节点
        return;
    }
    int mid = (l + r) >> 1;
    // 递归建立左右子树
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
}

// 下传,下传标记,同时改变dat数组
void pushdown(int rt, int l, int r) {
    if (lazy[rt]) {  // 如果有标记
        int mid = (l + r) >> 1;

        // 把标记给左右子树
        lazy[rt << 1] = lazy[rt];
        lazy[rt << 1 | 1] = lazy[rt];

        // rt标记清空
        lazy[rt] = 0;
    }
    return;
}

// 单点查询:a[l]
LL Query(int rt, int l, int r, int x) {
    if (l == r) return lazy[rt];  // 如果[l, r]被完全包含于[L, R]
    pushdown(rt, l, r);           // 标记下传
    // 递归加上左右子树
    int mid = (l + r) >> 1;
    if (x <= mid)
        return Query(rt << 1, l, mid, x);
    else
        return Query(rt << 1 | 1, mid + 1, r, x);
}

// 区间修改: [L, R] = x
void modify(int rt, int l, int r, int L, int R, int x) {
    if (L <= l && r <= R) {  // 如果当前区间被完全包含
        lazy[rt] = x;        // 改变懒标记
        return;
    }

    pushdown(rt, l, r);  // 下传
    // 递归左右子树修改区间
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, x);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, x);
    return;
}

int main() {
    while (scanf("%d", &n) != EOF) {
        memset(lazy, 0, sizeof lazy);
        memset(a, 0, sizeof a);
        s.clear();
        build(1, 1, N);

        int maxv = 0;
        for (int i = 1; i <= n; ++i) {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            a++, b++, c++;
            maxv = max(maxv, b);
            modify(1, 1, N, a, b - 1, c);
        }

        // 计算每个点的颜色
        for (int i = 1; i <= maxv - 1; ++i) {
            int tmp = Query(1, 1, N, i);
            a[i] = tmp;
        }

        // 统计每个分段的颜色
        for (int i = 1, j = 1; i <= maxv - 1; i = j) {
            j = i;
            while (j <= maxv && a[i] == a[j]) j++;
            if (a[i]) s[a[i]]++;
        }
        for (auto si : s) printf("%d %d\n", si.first - 1, si.second);
        printf("\n");
    }
    return 0;
}

HDU 4553 约会安排

这个大佬讲的很明白了,代码好多,想想就多不想写了:https://blog.csdn.net/laaahu/article/details/103170252

题意: 一个人拥有T的总时间, 和M件要做的事情。
三种事情:
一是屌丝基友约他出去玩, 他如果在某个时间段内有一段连续时间就和基友出去玩,相应的那段时间将会被占用。
二是女神约她出去, 他会在自己的安排结束的时间段内去找一段连续时间和女神去约会,相应的这段时间也被占用将不再被后面的事情使用。如果在这段时间里没有找到他就会去,找已经被基友占的时间,会选择放基友的鸽子,然后和女神去约会,当然和女神约会玩,基友的局还没结束则会去继续和基友浪,如果没找到只能和女神说拜拜了。
三是自己会然醒悟,要去学习,把自己的计划安排全部清空,不论是女神还是基友,但是就是清空,有人来约他照样会去。

题解:

线段树每个节点维护九个值:
d, n, s ------- 分别表示屌丝, 女神, 学习的懒惰标记。
两棵树放在一起维护:
dls, drs, dms ----- 分别表示区间从左端开始最长的连续可用区间,区间从右端开始连续的最长可用的区间,以及区间的最长连续区间的长度。(表示屌丝)。
nls, nrs, nms ----- 相应的表示女神。
每次更新的时候如果是屌丝就去屌丝的树上找,如果是女神去屌丝树上先找,如果找不到就去女神树上继续找。

**代码: **

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 1e5 + 10;

struct node {
    int b, e, s, n, d;
    int nls, nrs, nms;
    int dls, drs, dms;
} t[maxn << 2];
void push_up(int node) {
    if (t[node << 1].nls == t[node << 1].e - t[node << 1].b + 1) {
        t[node].nls = t[node << 1].nls + t[(node << 1) | 1].nls;
    } else {
        t[node].nls = t[node << 1].nls;
    }

    if (t[(node << 1) | 1].nrs ==
        t[(node << 1) | 1].e - t[(node << 1) | 1].b + 1) {
        t[node].nrs = t[(node << 1) | 1].nrs + t[node << 1].nrs;
    } else {
        t[node].nrs = t[(node << 1) | 1].nrs;
    }
    t[node].nms = max(max(t[node << 1].nms, t[(node << 1) | 1].nms),
                      t[node << 1].nrs + t[(node << 1) | 1].nls);

    if (t[node << 1].dls == t[node << 1].e - t[node << 1].b + 1) {
        t[node].dls = t[node << 1].dls + t[(node << 1) | 1].dls;
    } else {
        t[node].dls = t[node << 1].dls;
    }

    if (t[(node << 1) | 1].drs ==
        t[(node << 1) | 1].e - t[(node << 1) | 1].b + 1) {
        t[node].drs = t[(node << 1) | 1].drs + t[node << 1].drs;
    } else {
        t[node].drs = t[(node << 1) | 1].drs;
    }
    t[node].dms = max(max(t[node << 1].dms, t[(node << 1) | 1].dms),
                      t[node << 1].drs + t[(node << 1) | 1].dls);
}

void push_down(int node) {
    if (t[node].s) {
        t[node << 1].dls = t[node << 1].drs = t[node << 1].dms =
            t[node << 1].e - t[node << 1].b + 1;
        t[node << 1].nls = t[node << 1].nrs = t[node << 1].nms =
            t[node << 1].e - t[node << 1].b + 1;
        t[(node << 1) | 1].dls = t[(node << 1) | 1].drs =
            t[(node << 1) | 1].dms =
                t[(node << 1) | 1].e - t[(node << 1) | 1].b + 1;
        t[(node << 1) | 1].nls = t[(node << 1) | 1].nrs =
            t[(node << 1) | 1].nms =
                t[(node << 1) | 1].e - t[(node << 1) | 1].b + 1;
        t[node << 1].s = t[(node << 1) | 1].s = 1;
        t[node << 1].n = t[(node << 1) | 1].n = 0;
        t[node << 1].d = t[(node << 1) | 1].d = 0;
        t[node].s = 0;
    }
    if (t[node].n) {
        t[node << 1].nls = t[node << 1].nrs = t[node << 1].nms = 0;
        t[node << 1].dls = t[node << 1].drs = t[node << 1].dms = 0;
        t[(node << 1) | 1].nls = t[(node << 1) | 1].nrs =
            t[(node << 1) | 1].nms = 0;
        t[(node << 1) | 1].dls = t[(node << 1) | 1].drs =
            t[(node << 1) | 1].dms = 0;

        t[node << 1].n = t[(node << 1) | 1].n = 1;
        t[node << 1].d = t[(node << 1) | 1].d = 0;
        t[node].n = 0;
    }

    if (t[node].d) {
        t[node << 1].dls = t[node << 1].drs = t[node << 1].dms = 0;
        t[(node << 1) | 1].dls = t[(node << 1) | 1].drs =
            t[(node << 1) | 1].dms = 0;

        t[node << 1].d = t[(node << 1) | 1].d = 1;
        t[node].d = 0;
    }
}
void create(int bb, int ee, int node) {
    t[node].b = bb, t[node].e = ee;
    t[node].s = t[node].n = t[node].d = 0;
    if (bb == ee) {
        t[node].nls = t[node].nrs = t[node].nms = 1;
        t[node].dls = t[node].drs = t[node].dms = 1;
        return;
    }
    int mid = (bb + ee) >> 1;
    create(bb, mid, node << 1);
    create(mid + 1, ee, (node << 1) | 1);
    push_up(node);
}

void update(int bb, int ee, int node, int opt) {
    if (bb <= t[node].b && ee >= t[node].e) {
        if (opt == 1) {
            t[node].dls = t[node].drs = t[node].dms = 0;
            t[node].d = 1;
        }
        if (opt == 2) {
            t[node].dls = t[node].drs = t[node].dms = 0;
            t[node].nls = t[node].nrs = t[node].nms = 0;
            t[node].n = 1;
            t[node].d = 0;
        }
        if (opt == 3) {
            t[node].dls = t[node].drs = t[node].dms = t[node].e - t[node].b + 1;
            t[node].nls = t[node].nrs = t[node].nms = t[node].e - t[node].b + 1;
            t[node].s = 1;
            t[node].d = t[node].n = 0;
        }
        return;
    }
    push_down(node);
    int mid = (t[node].b + t[node].e) >> 1;
    if (bb > mid)
        update(bb, ee, (node << 1) | 1, opt);
    else if (ee <= mid)
        update(bb, ee, node << 1, opt);
    else {
        update(bb, mid, node << 1, opt);
        update(mid + 1, ee, (node << 1) | 1, opt);
    }
    push_up(node);
}

int query(int node, int opt, int k) {
    if (t[node].b == t[node].e) {
        return t[node].b;
    }
    push_down(node);
    if (opt == 1) {
        if (t[node << 1].dms >= k) {
            return query(node << 1, opt, k);
        } else if (t[node << 1].drs + t[(node << 1) | 1].dls >= k) {
            return t[node << 1].e - t[node << 1].drs + 1;
        } else {
            return query((node << 1) | 1, opt, k);
        }
    }
    if (opt == 2) {
        if (t[node << 1].nms >= k) {
            return query(node << 1, opt, k);
        } else if (t[node << 1].nrs + t[(node << 1) | 1].nls >= k) {
            return t[node << 1].e - t[node << 1].nrs + 1;
        } else {
            return query((node << 1) | 1, opt, k);
        }
    }
    return -1;
}

int main() {
    int tt, n, m, cnt = 0;
    scanf("%d", &tt);
    while (tt--) {
        scanf("%d %d", &n, &m);
        create(1, n, 1);
        printf("Case %d:\n", ++cnt);
        for (int i = 0; i < m; i++) {
            int tmp, l, r;
            char ch[20];
            scanf("%s", ch);
            if (ch[0] == 'D') {
                scanf("%d", &tmp);
                int k;
                if (t[1].dms >= tmp)
                    k = query(1, 1, tmp);
                else
                    k = -1;
                if (k == -1) {
                    printf("fly with yourself\n");
                } else {
                    printf("%d,let's fly\n", k);
                    update(k, k + tmp - 1, 1, 1);
                }
            }
            if (ch[0] == 'N') {
                scanf("%d", &tmp);
                int k;
                if (t[1].dms >= tmp)
                    k = query(1, 1, tmp);
                else
                    k = -1;
                if (k == -1) {
                    int kk;
                    if (t[1].nms >= tmp)
                        kk = query(1, 2, tmp);
                    else
                        kk = -1;
                    if (kk == -1) {
                        printf("wait for me\n");
                    } else {
                        printf("%d,don't put my gezi\n", kk);
                        update(kk, kk + tmp - 1, 1, 2);
                    }
                } else {
                    printf("%d,don't put my gezi\n", k);
                    update(k, k + tmp - 1, 1, 2);
                }
            }
            if (ch[0] == 'S') {
                scanf("%d %d", &l, &r);
                update(l, r, 1, 3);
                printf("I am the hope of chinese chengxuyuan!!\n");
            }
        }
    }
    return 0;
}

3.2 特殊构造题

2014-2015 ACM-ICPC, Asia Tokyo Regional Contest G.Flipping Parentheses
题意: 给定一个由’(‘和’)‘组成的字符串s,每次给定一个t,将s[t]变化为另一个种类的括号(左括号变成右括号,右括号变成左括号)。要求在t前找到一个tmp位置,使得s[tmp]变化为另一个种类的括号后,整个s字符串仍然平衡。平衡意味着s中的左括号都能在后面找到一个右括号和他匹配。 字 符 串 长 度 n < = 3 ∗ 1 0 5 , 询 问 次 数 m < = 1.5 ∗ 1 0 5 字符串长度n<=3*10^5,询问次数m<=1.5 * 10^5 n<=3105,m<=1.5105
题解: 对于这个括号序列,我们可以将’(‘设为1,’)‘设为-1,那么就能算出他们的前缀和,所有的前缀和都是大于等于0的。
如果我们改变一个’(‘并且他的位置是x,那么[x, n]范围内的前缀都-2。那么一旦找到其位置tmp以后,区间[tmp,n]上的前缀和都+2。所以只要在p之前找到一个’)‘将其转换就可以了。题目要求越靠左越好,那就从头开始找到第一个’)‘即可。注意到如果从头开始,还没出现’)‘之前,所有的’(‘的值都是和他本身相同的。
如果我们改变一个’)‘并且他的位置是x,那么[x, n]范围内的前缀都+2,我们同样的需要寻找一个’('将其转换。此时寻找其位置tmp的时候,要保证[tmp,n]后面所有的前缀和都要大于等于2。所以我们就寻找第一个满足条件的这个位置就好了。注意到只要右区间上最小的那个值都大于等于2,就可以往左区间去找。找到以后要加一。
代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 3e5 + 10;
int dat1[N << 2], lazy[N << 2], sum[N], n, m, dat2[N << 2]; // dat1=max, dat2 = min
char s[N];

void pushup(int rt) { 
    dat1[rt] = max(dat1[rt << 1], dat1[rt << 1 | 1]);
    dat2[rt] = min(dat2[rt << 1], dat2[rt << 1 | 1]);
    return;
}

void build(int rt, int l, int r) {
    if (l == r) {
        dat1[rt] = dat2[rt] = sum[l];
        lazy[rt] = 0;
        return;
    }
    int mid = (l + r) >> 1;
    build(rt << 1, l, mid);
    build(rt << 1 | 1, mid + 1, r);
    pushup(rt);
    return;
}

void pushdown(int rt, int l, int r) {
    if (lazy[rt]) {
        dat1[rt << 1] += lazy[rt], dat1[rt << 1 | 1] += lazy[rt], lazy[rt << 1] += lazy[rt];
        dat2[rt << 1] += lazy[rt], dat2[rt << 1 | 1] += lazy[rt], lazy[rt << 1 | 1] += lazy[rt];
        lazy[rt] = 0;
    }
    return;
}

void modify(int rt, int l, int r, int L, int R, int y) {
    if (L <= l && r <= R) {
        dat1[rt] += y;
        dat2[rt] += y;
        lazy[rt] += y;
        return;
    }
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    if (L <= mid) modify(rt << 1, l, mid, L, R, y);
    if (mid < R) modify(rt << 1 | 1, mid + 1, r, L, R, y);
    pushup(rt);
    return;
}

int query1(int rt, int l, int r) {
    if (l == r) return l;
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    if (dat1[rt << 1] != mid) return query1(rt << 1, l, mid);   //如果从头开始,还没出现')'之前,所有的'('的值都是和他本身相同的。
    else return query1(rt << 1 | 1, mid + 1, r);
}

int query2(int rt, int l, int r) {
    if (l == r) return l;
    pushdown(rt, l, r);
    int mid = (l + r) >> 1;
    if (dat2[rt << 1 | 1] >= 2) return query2(rt << 1, l, mid);   //只要右区间上最小的那个值都大于等于2,就可以往左区间去找。找到以后要加一。
    else return query2(rt << 1 | 1, mid + 1, r);
}

int main() {
    cin >> n >> m;
    scanf("%s", s + 1);
    int len = strlen(s + 1);
    for (int i = 1; i <= len; ++i) {
        if (s[i] == '(') sum[i] = sum[i - 1] + 1;
        else sum[i] = sum[i - 1] - 1;
    }
    build(1, 1, n);
    for (int i = 1, t; i <= m; ++i) {
        scanf("%d", &t);
        if (s[t] == '(') {
            modify(1, 1, n, t, n, -2);
            s[t] = ')';
            int tmp = query1(1, 1, n);
            s[tmp] = '(';
            modify(1, 1, n, tmp, n, 2);
            printf("%d\n", tmp);
        }
        else {
            modify(1, 1, n, t, n, 2);
            s[t] = '(';
            int tmp = query2(1, 1, n) + 1;
            s[tmp] = ')';
            modify(1, 1, n, tmp, n, -2);
            printf("%d\n", tmp);
        }
    }
    return 0;
}

Codeforces Round #684 (Div. 2) E.Greedy Shopping

题意: 有n个数字,这n个数字单调不增,有m个操作:

操作1: 1 x y, 把前1 ~ x个数字都与y取max

操作2:2 x y, 询问从第x个数字开始往后,最多有几个数字的和小于等于y

题解: 可以使用线段树维护区间和,区间长度,区间最小值和最大值。对于操作1,就是区间修改;对于操作2,如果当前的y小于区间最小值,那么直接return,如果当前y大于当前区间和,那么直接把区间和减掉y。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10, NN = N * 4;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T;
LL sum[NN];
int len[NN], minv[NN], maxv[NN], lazy[NN], a[N];

void pushup(int u) {
    sum[u] = sum[u << 1] + sum[u << 1 | 1];
    len[u] = len[u << 1] + len[u << 1 | 1];
    minv[u] = min(minv[u << 1], minv[u << 1 | 1]);
    maxv[u] = max(maxv[u << 1], maxv[u << 1 | 1]);
    return;
}

void build(int u, int l, int r) {
    if (l == r) {
        sum[u] = a[l], len[u] = 1, maxv[u] = minv[u] = a[l];
        return ;
    }
    int mid = (l + r) >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r) ;
    pushup(u);
}

void pushdown(int u, int l, int r) {
    int mid = (l + r) >> 1;
    if (!lazy[u]) return ;
    minv[u << 1] = minv[u << 1 | 1] = maxv[u << 1] = maxv[u << 1 | 1] = lazy[u];
    sum[u << 1] = (LL)len[u << 1] * lazy[u], sum[u << 1 | 1] = (LL)len[u << 1 | 1] * lazy[u];
    lazy[u << 1] = lazy[u << 1 | 1] = lazy[u], lazy[u] = 0;
    return;
}

void modify(int u, int l, int r, int L, int R, int y) {
    if (minv[u] >= y) return;
    if (l >= L && r <= R && maxv[u] < y) {
        sum[u] = (LL)y * len[u], minv[u] = maxv[u] = y, lazy[u] = y;
        return;
    }
    pushdown(u, l, r);
    int mid = (l + r) >> 1;
    if (L <= mid) modify(u << 1, l, mid, L, R, y);
    if (mid < R) modify(u << 1 | 1, mid + 1, r, L, R, y);
    pushup(u);
    return;
}

int query(int u, int l, int r, int L, int R, int &y) {
    if (minv[u] > y) return 0;
    if (l >= L && r <= R && sum[u] <= y) {
        y -= sum[u];
        return len[u];
    }
    pushdown(u, l, r);
    int mid = (l + r) >> 1;
    int res = 0;
    if (L <= mid) res += query(u << 1, l, mid, L, R, y);
    if (mid < R) res += query(u << 1 | 1, mid + 1, r, L, R, y);
    return res;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); 
    build(1, 1, n);
    for (int i = 1; i <= m; ++i) {
        int op, x, y;
        scanf("%d%d%d", &op, &x, &y);
        if (op == 1) modify(1, 1, n, 1, x, y);
        else printf("%d\n", query(1, 1, n, x, n, y));
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值