【数据结构】数据结构学习+刷题

本文详细介绍了线段树在解决区间查询和修改问题中的应用,包括ACwing平台上的线段树题目,如最大数、区间最大公约数、亚特兰蒂斯等,以及洛谷平台上的相关题目。文章通过多个实例分析了线段树的结构、更新、查询、建树等操作,并探讨了树状数组在逆序对、区间和等场景下的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

数据结构

一、线段树

ACwing

1.1275最大数(AcWing)

题目链接:https://www.acwing.com/problem/content/1277/
题意:给定一个有n个数的正整数序列,你可以进行两种操作:1.添加操作:想序列后添加一个数,序列长度变为n+1。2.询问操作:询问这个序列中最后L个数中最大的数是多少。一共有m次操作,输出每次的询问操作的答案。
思路:这道题就只有一个坑点,就是在添加的时候,每次都是(t + a) % p,所以你要用一个变量来存储上一次询问的值即可。其它的就是线段树的模板。接下来说一下这道题涉及到的线段树的几个模板:
1.先说一下线段树的每一个节点所需要存储那些信息,首先是必然包含左儿子和右儿子,然后其他的信息就是根据题意来增加了,在这个题中,它的询问操作是一个范围内的最大值,所以节点还需要存储一个最大值,简单来说,某个节点所存储的信息能够从它的左右儿子推导而来。

struct node
{
	int l, r;
	int v;
};

2.更新模板:更新某个节点的值。在这个题就是更新某个节点的最大值。

void pushup(int u)
{
	tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}

3.建树模板:建树的过程就是一个递归的过程,只要是没有到达叶子节点,就一直递归。

void build(int u, int l, int r)
{
	tr[u] = {l, r};
	if (l == r) return ;///叶子节点
	int mid = l + r >> 1;
	build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

4.查询模板:这里先贴一个图就比较好理解:
在这里插入图片描述
假如我们现在建了一个范围是在1-10的树,那么我们现在要找1 - 6之间的最大值,那么我们是不是要将1-6分为1-5和6这两段里面去找,后面这个我们一眼就能看出是6,但是在代码中我们又怎么实现呢?首先我们要一直记住我们要找的范围是1-6,假如当前的节点的范围被1-6所包含,那么此时我们是不是就该直接返回当前节点的最大值,假如我们现在要求6-10之间的最大值,而我们现在的节点是6-8这个节点,那么此时的最大值是不是6-8的最值,然后找到9-10的最值,两者作比较就得到了最值,那么我们就证明到了包含的这种情况。然后,我们再找当前节点和要寻找的范围之间有没有交集,那边有交集就往那边找。下面我们直接看代码。

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
    int v = 0;
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) v = query(u << 1, l, r);
    if (r > mid) v = max(v, query(u << 1 | 1, l, r));

    return v;
}

5.修改模板:我们是将x这个节点的值修改为v,就是要注意的一点是,在每次修改完左儿子和右儿子之后不要忘了更新父节点

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
    int v = 0;
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) v = query(u << 1, l, r);
    if (r > mid) v = max(v, query(u << 1 | 1, l, r));

    return v;
}

AC代码

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int N = 200010;
struct node
{
    int l, r;
    int v;
}tr[N * 4];
int m, p;

void pushup(int u)
{
    tr[u].v = max(tr[u << 1].v, tr[u << 1 | 1].v);
}

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

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].v;
    int v = 0;
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) v = query(u << 1, l, r);
    if (r > mid) v = max(v, query(u << 1 | 1, l, r));

    return v;
}

void modify(int u, int x, int v)
{
    if (tr[u].l == x && tr[u].r == x) tr[u].v = v;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

signed main()
{
    int n = 0, last = 0;
    scanf("%d%d", &m, &p);
    build(1, 1, m);

    int x;
    char op[2];
    while (m --)
    {
        scanf("%s%d", op, &x);
        if (*op == 'Q') {last = query(1, n - x + 1, n); printf("%d\n", last); }
        else {modify(1, n + 1, (last + x) % p); n ++;}
    }

    return 0;
}

2.245. 你能回答这些问题吗

题目链接:https://www.acwing.com/problem/content/246/
题意:给你一个长度为n的数列,然后又m条指令,指令有两种:1.1 x y,查询区间[x,y]中的最大连续子段和。2.2 x y,把A[x]改成y。对于每一条查询指令输出查询的答案。
思路:首先考虑每个节点应该包含那些信息,左右儿子l,r肯定是包含的,然后最大连续子段和tmax肯定是包含的,接下来我们想一下最大连续子段和该怎么计算,我们先来看一个图
在这里插入图片描述
就上面的三个量,是不够推到的,假设要求的范围是上面的红括号内,那么这个时候的最大连续子段的合适1到左括号+2到右括号,所以还需要添加左范围的以1为结点的后缀和,加上以2为开始的到右括号的前缀和,所以还需要添加的是以lmax(最大后缀和)rmax(最大前缀和),那么求某个点开始的前缀和也会有两种情况,那么是只在前半段内或者包含了右边范围的一点,要么是rmax或者是sum+lmax,所以还需要添加三个变量,sum,lmax,rmax。

struct node
{
	int l, r;
	int sum, lmax, rmax, tmax;
}tr[N * 4];

接下来我们看更新操作,对于每一个结点我们需要维护的值有四个,sum,tmax,rmax,lmax。现在来看一下lmax怎么计算(rmax也是同理)
在这里插入图片描述
假设要求的前缀和是上面1,那么就是lmax,如果是第二种情况,那么就是l_sum + r_lmax,rmax也同理。我们又来看一下tmax怎么更新,现在左右儿子中的找到最大的tmax,或者是l.rmax+r.lmax。
补充:这里的最大前缀和和最大后缀和我们都是以某个节点的终点来找的。
上面我们找到了每个节点应该包含的信息,现在我们来看更新模板,我们要更新的是每个节点的最大连续子段和,那么就是找父节点和它的两个儿子的最大连续子段和的最大值,所以这里应该这样写:

void pushup(node &u, node &l, node &r)
{
	u.sum = l.sum + r.sum;
	u.lmax = max(l.max, l.max + r.lmax);
	u.rmax = max(r.max, r.sum + l.rmax);
	u.tmax = max(max(l.tmax, r.tmax), ;.rmax + r.lmax);
}
void push(int u)
{
	pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

建树和修改和之前的都差不多,唯一一点区别就是需要更新,我们现在来看一下查找操作,我们在查找的时候要记录四个量,所以我们干脆直接返回一个结构体,我们要考虑三种情况,只在左边,只在右边,或者横穿,前面两种情况好些,但是横穿这种情况,我们要找到左边的,在找到右边的,最后在更新,在返回。看代码:

Node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);
        else if (l > mid) return query(u << 1 | 1, l, r);
        else
        {
            auto left = query(u << 1, l, r);
            auto right = query(u << 1 | 1, l, r);
            Node res;
            pushup(res, left, right);
            return res;
        }
    }
}

AC代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 500010;

int n, m;
int w[N];
struct Node
{
    int l, r;
    int sum, lmax, rmax, tmax;
}tr[N * 4];

void pushup(Node &u, Node &l, Node &r)
{
    u.sum = l.sum + r.sum;
    u.lmax = max(l.lmax, l.sum + r.lmax);
    u.rmax = max(r.rmax, r.sum + l.rmax);
    u.tmax = max(max(l.tmax, r.tmax), l.rmax + r.lmax);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, w[r], w[r], w[r], w[r]};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int x, int v)
{
    if (tr[u].l == x && tr[u].r == x) tr[u] = {x, x, v, v, v, v};
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

Node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);
        else if (l > mid) return query(u << 1 | 1, l, r);
        else
        {
            auto left = query(u << 1, l, r);
            auto right = query(u << 1 | 1, l, r);
            Node res;
            pushup(res, left, right);
            return res;
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    build(1, 1, n);

    int k, x, y;
    while (m -- )
    {
        scanf("%d%d%d", &k, &x, &y);
        if (k == 1)
        {
            if (x > y) swap(x, y);
            printf("%d\n", query(1, x, y).tmax);
        }
        else modify(1, x, y);
    }

    return 0;
}

3.AcWing 246. 区间最大公约数

题目链接:https://www.acwing.com/problem/content/247/
题意:给你一个长度为n的数列A,以及m条指令。1.c l r d,表示把A[l],A[l+1],……,A[r]都加上d。2.Q l r,表示询问A[l],A[l+1]……A[r]的最大公约数,对于每个询问,输出答案。
思路:对于第一条指令,它是将一个连续的范围都加上一个数,很快就想到了差分,那么对于求一个范围[L,R]的最大公约数,我们可以求gcd(a[L],gcd(b[L+1],b[L+2]……b[R])),最大公约数具有结合律、分配律,(b数组是差分数组)。我们首先想一个每一个结点应该包含那些信息,首先肯定有左右儿子以及最大的公约数,但是我们还可以把差分数组包含在结构体内,就不用单独来储存了。

struct node
{
	int l, r;
	ll sum, d;///sum表示每个数对应的差分数组,d表示最大公约数。
}

我们再继续看查询,查询是在找最大的公约数,这里还是包含了三种情况,1.当前结点只在左边范围。2.当前节点只在右边范围。3.范围横跨当前结点的范围。所有查询该为:

node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);
        else if (l > mid) return query(u << 1 | 1, l, r);
        else
        {
            auto left = query(u << 1, l, r);
            auto right = query(u << 1 | 1, l, r);
            node res;
            pushup(res, left, right);
            return res;
        }
    }
}

更新函数,需要更新的是差分和以及最大公约数,最大公数等于两个数的最大公约数的公约数:

void pushup(node &u, node &l, node &r)
{
    u.sum = l.sum + r.sum;
    u.d = gcd(l.d, r.d);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

在说一下修改函数,因为这里的修改是在一个范围内添加一个数,所以我们想到了差分,所以修改函数为:

void modify(int u, int x, ll v)
{
    if (tr[u].l == x && tr[u].r == x) {ll b = tr[u].sum + v; tr[u] = {x, x, b, b}; }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

AC代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 500010;

struct node
{
    int l, r;
    ll sum, d;
}tr[N * 4];

int n, m;
ll w[N];

ll gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

void pushup(node &u, node &l, node &r)
{
    u.sum = l.sum + r.sum;
    u.d = gcd(l.d, r.d);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    if (l == r) {ll b = w[r] - w[r - 1]; tr[u] = {l, r, b, b};}
    else
    {
        tr[u].l = l, tr[u].r = r;
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int x, ll v)
{
    if (tr[u].l == x && tr[u].r == x) {ll b = tr[u].sum + v; tr[u] = {x, x, b, b}; }
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (x <= mid) modify(u << 1, x, v);
        else modify(u << 1 | 1, x, v);
        pushup(u);
    }
}

node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);
        else if (l > mid) return query(u << 1 | 1, l, r);
        else
        {
            auto left = query(u << 1, l, r);
            auto right = query(u << 1 | 1, l, r);
            node res;
            pushup(res, left, right);
            return res;
        }
    }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> w[i];
    build(1, 1, n);

    int l, r;
    ll d;
    char op[2];
    while (m --)
    {
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'Q')
        {
            auto left = query(1, 1, l);
            node right = {0, 0, 0, 0};
            if (l + 1 <= r) right = query(1, l + 1, r);
            printf("%lld\n", abs(gcd(left.sum, right.d)));
        }
        else
        {
            scanf("%lld", &d);
            ///差分
            modify(1, l, d);
            if (r + 1 <= n) modify(1, r + 1, -d);
        }
    }

    return 0;
}

4.AcWing 247. 亚特兰蒂斯

题目链接:https://www.acwing.com/problem/content/249/
题意:给你描绘了一个地图,地图上的图形都是矩形,有可能有重合,问你这些矩形的面积是多少。
思路:线段树+扫描线。
如图,假设是给定的图形:
在这里插入图片描述
那么我们变换一下,得到了应该求的面积的区域:
在这里插入图片描述
我们将重合的部分去掉,就得到了实际的面积区域,我们在做如下变换:
在这里插入图片描述
那么我们要求的面积就是图中绿色的阴影部分,那么我们现在的问题就是每一个竖条间的阴影部分的高度是多少,因为两个竖条之间的距离好找,现在的难点就是找高度。我们现在可以在纵坐标上做一个线段树,在此之前我们还需要做一个操作,就是把阴影部分的每一条竖着的边加上一个权值,看成一个带边权的有向边,如下图:
在这里插入图片描述
这样我们就把每条边看作了一个有权值的边。然后我们只需要在每一个竖边对应的y轴上加上它的权值,这样我们从左往右看,就可以把每个边看作一个操作。然后我们通过看每个线段内的权值,就可以知道当前区间被多少个矩形覆盖,那么我们现在可以进行两个操作:1.将某个区间[L,R] + k(k = 1或者k = -1)。2.整个区间中,长度大于0的区间总长是多少。那么我们线段树所需要维护的有:cnt,被覆盖的次数,len,不考虑祖先节点cnt的前提下,cnt>0的区间总长。现在我们要知道扫描线的几个性质:1.永远只考虑根节点的信息。2.所有的操作都是成对出现的,且先加后减。(这里就说明了可以不用pushdown)。我们使用扫描线来求面积,顾名思义,就是用一根竖直的线从左向右的扫描,但后的动态的保存面积,只有是遇到区间的cnt是大于0的就更新面积。
那么我们现在来看一下每个节点所包含的信息,左右儿子,当前区间出现的次数,以及当前区间的长度,所以一共包含了四个量:

struct node
{
    int l, r;
    int cnt;
    double len;
}tr[N * 8];

这里在补充一下,因为题目中说明了坐标不一定是整数,那么可能就会有小数,所以这里我们需要离散化来保存坐标:

vector<double> ys;
int find(double x)
{
    return lower_bound(ys.begin(), ys.end(), x) - ys.begin();
}

继续,我们上面找到了线段树上每个线段的信息,现在我们要考虑扫描线的信息了

struct Segment
{
    double x, y1, y2;
    int k;///判断是那个竖边
    bool operator < (const Segment &t) const
    {
        return x < t.x;
    }
}seg[N * 2];

接下来我们先看主函数,因为我们每次都是从根节点获得信息,根节点中保存了当前状态下y轴的那些区间出现了的次数,我们在跟新了答案之后还需要进行操作,因为可能遇到边权是-1的竖边,所以我们需要修改,如下:

double res = 0;
        for (int i = 0; i < n * 2; i++)
        {
            if (i > 0) res += tr[1].len * (seg[i].x - seg[i - 1].x);
            modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);
        }

然后是pushup函数,这里要分成这么几种情况:1.完全覆盖,那么此时的tr[u].cnt大于0,那么可以直接得到区间长度,2.tr[u].cnt = 0,但是不是叶子节点,那么父节点的长度就等于两个儿子节点的长度,3.是叶子节点,那么长度就等于0。所以pushup函数就是:

void pushup(int u)
{
    ///完全覆盖,
    if (tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];
    ///不完全覆盖,cnt = 0,首先判断是不是叶节点。
    else if (tr[u].l != tr[u].r) tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
    else tr[u].len = 0;///叶子节点
}

AC代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

struct Segment
{
    double x, y1, y2;
    int k;///判断是那个竖边
    bool operator < (const Segment &t) const
    {
        return x < t.x;
    }
}seg[N * 2];

struct node
{
    int l, r;
    int cnt;
    double len;
}tr[N * 8];

int n;
vector<double> ys;

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

void pushup(int u)
{
    ///完全覆盖,
    if (tr[u].cnt) tr[u].len = ys[tr[u].r + 1] - ys[tr[u].l];
    ///不完全覆盖,cnt = 0,首先判断是不是叶节点。
    else if (tr[u].l != tr[u].r) tr[u].len = tr[u << 1].len + tr[u << 1 | 1].len;
    else tr[u].len = 0;///叶子节点
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 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 k)
{
    if (tr[u].l >= l && tr[u].r <= r) {tr[u].cnt += k; pushup(u);}
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, k);
        if (r > mid) modify(u << 1 | 1, l, r, k);
        pushup(u);
    }
}

int main()
{
    int T = 1;
    while (cin >> n, n)
    {
        ys.clear();
        for (int i = 0, j = 0; i < n; i++)
        {
            double x1, y1, x2, y2; cin >> x1 >> y1 >> x2 >> y2;
            seg[j ++] = {x1, y1, y2, 1}, seg[j ++] = {x2, y1, y2, -1};
            ys.push_back(y1), ys.push_back(y2);
        }

        sort(ys.begin(), ys.end());
        ys.erase(unique(ys.begin(), ys.end()), ys.end());

        build(1, 0, ys.size() - 2);///建树,0开始,-2。
        sort(seg, seg + n * 2);

        double res = 0;
        for (int i = 0; i < n * 2; i++)
        {
            if (i > 0) res += tr[1].len * (seg[i].x - seg[i - 1].x);
            modify(1, find(seg[i].y1), find(seg[i].y2) - 1, seg[i].k);
        }

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

    return 0;
}

5.AcWing 1277. 维护序列

题目链接:https://www.acwing.com/problem/content/1279/
题意:这个跟第二题类似,只不过操作边长了三种:1.把数列中的一段数全部乘一个值;2.把数列中的一段数全部加一个值;3.询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 P 的值。
思路:这个题主要是pushdown有一些改变,我们向下维护,是要维护当前节点的两个儿子节点,然后要把ada = 0,mul = 1,然后我们在单独建一个函数来计算向下维护的节点的add、mul以及sum值,代码如下:

void eval(node &t, int add, int mul)
{
    t.sum = ((ll)t.sum * mul + (ll)(t.r - t.l + 1) * add) % p;
    t.mul = (ll)t.mul * mul % p;
    t.add = ((ll)t.add * mul + add) % p;
}

void pushdown(int u)
{
    eval(tr[u << 1], tr[u].add, tr[u].mul);
    eval(tr[u << 1 | 1], tr[u].add, tr[u].mul);
    tr[u].add = 0, tr[u].mul = 1;
}

AC代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;
typedef long long ll;

struct node
{
    int l, r;
    ll sum, add, mul;
}tr[N * 4];

int n, p, m;
int a[N];

void pushup(int u)
{
    tr[u].sum = (tr[u << 1].sum + tr[u << 1 | 1].sum) % p;
}

void eval(node &t, int add, int mul)
{
    t.sum = ((ll)t.sum * mul + (ll)(t.r - t.l + 1) * add) % p;
    t.mul = (ll)t.mul * mul % p;
    t.add = ((ll)t.add * mul + add) % p;
}

void pushdown(int u)
{
    eval(tr[u << 1], tr[u].add, tr[u].mul);
    eval(tr[u << 1 | 1], tr[u].add, tr[u].mul);
    tr[u].add = 0, tr[u].mul = 1;
}

void build(int u, int l, int r)
{
    if(l == r) tr[u] = {l, l, a[r], 0, 1};
    else
    {
        tr[u] = {l, r, 0, 0, 1};
        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 add, int mul)
{
    if (tr[u].l >= l && tr[u].r <= r) eval(tr[u], add, mul);
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, add, mul);
        if (r > mid) modify(u << 1 | 1, l, r, add, mul);
        pushup(u);
    }
}

ll query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum % p;
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        ll sum = 0;
        if (l <= mid) sum = query(u << 1, l, r);
        if (r > mid) sum += query(u << 1 | 1, l, r);
        return sum % p;
    }
}

int main()
{
    cin >> n >> p;
    for (int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);

    cin >> m;
    int op, l, r, c;
    while (m --)
    {
        cin >> op >> l >> r;
        if (op == 1) {cin >> c; modify(1, l, r, 0, c);}
        else if (op == 2) {cin >> c; modify(1, l, r, c, 1);}
        else cout << query(1, l, r) % p << endl;
    }

    return 0;
}

kuangbin

1.敌兵布阵(HDU - 1166)

题目链接: https://vjudge.net/problem/HDU-1166
题意:告诉你了n个阵营,并且初始的人数,接下来会执行一些指令,指令一共有四种:1.add i j,表示第i个营地增加j个人。2.sub i j,表示第i个营地减少j个人。3.query i j,表示询问第i到j个营地的总人数。4.end表示结束。让你回答每一条询问指令。
思路:首先这个线段树存储的是每个阵营的信息,那就那就是一个点。那就结束了。

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

const int N = 50010;

struct node
{
    int l, r;
    int sum;
}tr[N * 4];

int n, a[N];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, a[r]};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int pos, int v)
{
    if (tr[u].l == tr[u].r && tr[u].l == pos)
        tr[u].sum += v;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (pos <= mid) modify(u << 1, pos, v);
        else modify(u << 1 | 1, pos, v);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        int sum = 0;
        if (l <= mid) sum = query(u << 1, l, r);
        if (r > mid) sum += query(u << 1 | 1, l, r);
        return sum;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    int t; cin >> t;
    for (int i = 1; i <= t; i++)
    {
        cin >> n;
        for (int i = 1; i <= n; i++) cin >> a[i];
        build(1, 1, n);

        char op[10];
        cout << "Case " << i << ":" << endl;
        while (scanf("%s", op) != EOF)
        {
            int a, b;
            if (*op == 'A') {cin >> a >> b; modify(1, a, b);}
            else if (*op == 'S') {cin >> a >> b; modify(1, a, -b);}
            else if (*op == 'Q') {cin >> a >> b; cout << query(1, a, b) << endl;}
            else if (*op == 'E') break;
        }
    }

    return 0;
}

2.I Hate It(HDU - 1754)

题目链接:https://vjudge.net/problem/HDU-1754
题意: 跟上一个题一样。
思路:跟上一个题一样。

#include <iostream>
#include <cstdio>
#include <cmath>

using namespace std;

const int N = 200010;
struct node
{
    int l, r;
    int max_g;
}tr[N * 4];

int n, m;
int a[N];

void pushup(int u)
{
    tr[u].max_g = max(tr[u << 1].max_g, tr[u << 1 | 1].max_g);
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, a[r]};
    else
    {
        tr[u] = {l, r};
        int mid = l + r >> 1;
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void modify(int u, int pos, int val)
{
    if (tr[u].l == tr[u].r && tr[u].r == pos) {tr[u].max_g = val; a[pos] = val;}
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (pos <= mid) modify(u << 1, pos, val);
        else modify(u << 1 | 1, pos, val);
        pushup(u);
    }
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].max_g;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        int max_grade = 0;
        if (l <= mid) max_grade = query(u << 1, l, r);
        if (r > mid) max_grade = max(max_grade, query(u << 1 | 1, l, r));
        return max_grade;
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);

    while (scanf("%d%d", &n, &m) != EOF)
    {
        for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
        build(1, 1, n);

        char op[2];
        int a, b;
        while (m--)
        {
            scanf("%s%d%d", op, &a, &b);
            if (*op == 'Q') printf("%d\n", query(1, a, b));
            else if (*op == 'U') modify(1, a, b);
        }
    }

    return 0;
}

3.Mayor’s posters(POJ - 2528)

题目链接:https://vjudge.net/problem/POJ-2528
题意:有n个人贴海报,给出n张海报所贴的范围l、r,求最后还能看见多少张海报。
思路:只要能看见颜色,都算能够看到海报。首先海报贴的范围是10000000,我们如果用一个数组来存储肯定会爆,所以我们需要离散化。首先我们想一下,线段树表示的是什么,需要维护那些信息。我们线段树肯定表示的是一个区间内贴的是那种海报,并且海报所表示的颜色,所以我们需要维护的信息有:区间+颜色。

struct node
{
    int l, r;
    int id;
}tr[N * 4];

我们先来看一下离散化的过程,我们用一个vector<int> v;来表示离散化的数组,而这里我们需要注意一点,假设给定的区间是[1,10] [1,4] [6,10],我们在离散化之后得到的结果是:1 4 6 10 => 1 2 3 4,那么所对应的区间是:[1, 4], [1, 2], [3, 4],那么我们会发现,与原来的相比,离散化后的数字是连续的,中间会有覆盖,而原来的是没有覆盖的,原来的答案是3,而离散后的答案是2,所以对于离散之前的,没有被覆盖的,我们在离散后需要表示出来,那么我们就只需要将它们之间隔开即可。具体看代码:

int find(int x)
{
    return lower_bound(v.begin(), v.end(), x) - v.begin();
}
///在主函数中,主要代码。
		sort(v.begin(), v.end());
        v.erase(unique(v.begin(), v.end()), v.end());
        ///我们这里循环,看响铃的两个数字,如果他原本的值不是是连续,那么我们在离散后会变成连续的,为了避免这种情况,我们只需要在添加一个比它小1的数将这两个数隔开即可。
        for (int i = 1; i <= n; i++)
            if (v[i] - v[i - 1] != 1) v.push_back(v[i] - 1);
        sort(v.begin(), v.end());

我们继续,这个题只需要向下更新不需要向上更新,因为父节点的颜色会向下传给左右儿子节点,而不需要向上传递更新信息,所以这里只有pushdown:

void pushdown(int u)
{
    if (tr[u].id)
    {
        tr[u << 1].id = tr[u << 1 | 1].id = tr[u].id;
        tr[u].id = 0;
    }
}

接下来,题中给定了n个海报的区间,那么我们只需要按顺序把每个区间染色即可,这里我们用了一个vector<pair<int, int> > area;来表示贴的区间,接下来我们直接按顺序修改即可:

void modify(int u, int l, int r, int c)
{
    if (tr[u].l >= l && tr[u].r <= r) tr[u].id = c;
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, c);
        if (r > mid) modify(u << 1 | 1, l, r, c);
    }
}

for (int i = 0; i < n; i++)
        {
            int l = area[i].first, r = area[i].second;
            l = find(l), r = find(r);
            modify(1, l, r, i + 1);
        }

最后一步就是找可以看见多少个海报的步骤了,就是查询操作,我们只需要看一共出现了几种颜色就判断除了可以看到几幅画,所以这里我们需要一个vis[N]数组来表示某种颜色是否出现过,看代码:

int query(int u)
{
    if (tr[u].id)
    {
        if (vis[tr[u].id]) return 0;
        return vis[tr[u].id] = 1;
    }

	///叶子节点就拜拜
    if (tr[u].l == tr[u].r) return 0;
	///继续递归左右儿子
    return query(u << 1) + query(u << 1 | 1);
}

AC代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 1e5 + 10;

struct node
{
    int l, r;
    int id;
}tr[N * 4];

bool vis[N];
vector<int> v;
vector<pair<int, int> > area;

int find(int x)
{
    return lower_bound(v.begin(), v.end(), x) - v.begin();
}

void pushdown(int u)
{
    if (tr[u].id)
    {
        tr[u << 1].id = tr[u << 1 | 1].id = tr[u].id;
        tr[u].id = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0};
    if (l == r) return ;
    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 c)
{
    if (tr[u].l >= l && tr[u].r <= r) tr[u].id = c;
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, c);
        if (r > mid) modify(u << 1 | 1, l, r, c);
    }
}

int query(int u)
{
    if (tr[u].id)
    {
        if (vis[tr[u].id]) return 0;
        return vis[tr[u].id] = 1;
    }

    if (tr[u].l == tr[u].r) return 0;

    return query(u << 1) + query(u << 1 | 1);
}

int main()
{
    int t; cin >> t;
    while (t --)
    {
        v.clear();v.push_back(-0x3f3f3f3f);
        area.clear();

        int n; cin >> n;
        for (int i = 1; i <= n; i++)
        {
            vis[i] = 0;
            int l, r; cin >> l >> r;
            v.push_back(l), v.push_back(r);
            area.push_back({l, r});
        }

        sort(v.begin(), v.end());
        v.erase(unique(v.begin(), v.end()), v.end());
        for (int i = 1; i <= n; i++)
            if (v[i] - v[i - 1] != 1) v.push_back(v[i] - 1);
        sort(v.begin(), v.end());

		///因为在最开始的时候我们插入了-1,所以这里是v.size()-1
        build(1, 1, v.size() - 1);

        for (int i = 0; i < n; i++)
        {
            int l = area[i].first, r = area[i].second;
            l = find(l), r = find(r);
            modify(1, l, r, i + 1);
        }

        cout << query(1) << endl;
    }

    return 0;
}

4.Just a Hook(HDU - 1698)

题目链接:https://vjudge.net/problem/HDU-1698
题意:简单来说,有一个长度为n的链条,每一段链条可以更改材料,铜=1,银=2,金=3,现在 m个操作,改变某个范围内的链条的材料,问你最终的值是多少。
思路:线段树单点查询,因为它问你的是最终的值,所以最后只需要输出根节点的值。那么这里我们需要维护的是,每一个节点的材料以及值的和。所以结构体为:

struct node
{
	int l, r;
	ll sum val;
}tr[N << 2];

然后这里同时需要向上和向下维护。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010;
typedef long long ll;

struct node
{
    int l, r;
    int val;
    ll sum;
}tr[N << 2];
int n, m;

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u)
{
    node &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if (root.val)
    {
        left.val = root.val, left.sum = (ll)(left.r - left.l + 1) * root.val;
        right.val = root.val, right.sum = (ll)(right.r - right.l + 1) * root.val;
        root.val = 0;
    }
}

void build(int u, int l, int r)
{
    if (l == r) tr[u] = {l, r, 1, 1};
    else
    {
        tr[u] = {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 val)
{
    if (tr[u].l >= l && tr[u].r <= r) {tr[u].sum = (ll)(tr[u].r - tr[u].l + 1) * val; tr[u].val = val;}
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, val);
        if (r > mid) modify(u << 1 | 1, l, r, val);
        pushup(u);
    }
}

int main()
{
    int t; scanf("%d", &t);
    for (int i = 1; i <= t; i++)
    {
        memset(tr, 0, sizeof tr);
        scanf("%d%d", &n, &m);
        build(1, 1, n);

        while (m--)
        {
            int x, y, z; scanf("%d%d%d", &x, &y, &z);
            modify(1, x, y, z);
        }

        printf("Case %d: The total value of the hook is %d.\n", i, tr[1].sum);
    }

    return 0;
}

5.Count the Colors(ZOJ - 1610)

题目链接:https://vjudge.net/problem/ZOJ-1610
题意:有一面墙让你去刷,颜色能够覆盖,如果一段连续的区间颜色一致,那么认为这种颜色只在这个区间出现了一次,现在让你统计颜色出现的次数。涂色是从[l+1, r]。
思路:首先我们要想一下每个区间需要维护的信息,很显然只需要维护做右端点和颜色。(做了这么几道线段树,今天重新思考了一下pushdown这个操作的意义以及该在什么时候使用pushdown,大家也可以思考一下。)那么我们的结构体就是:

struct node
{
	int l, r;
	int color;
}tr[N << 2];

这个题,我感觉难一点的就是离散化的过程,(这里的数据挺小的,可以不用离散化其实),如果离散化后每次找值都是用lower_bound,这个函数的时间复杂度是nlogn,所需要的时间很长,然后题目中说了连续的颜色只算一次,所以这里我们需要打个标记,记录上一次出现的颜色,只要不是连续的就++。
其次,这个题不需要向上维护,因为没有需要向上维护的东西。看代码:
AC代码

#include <bits/stdc++.h>

using namespace std;

const int N = 1e4 + 10;

int n;
int cnt[N];
int last;

struct node
{
    int l, r;
    int laz;
}tr[N << 2];

void pushdown(int u)
{
    if (tr[u].laz)
    {
        tr[u << 1].laz = tr[u].laz;
        tr[u << 1 | 1].laz = tr[u].laz;
        tr[u].laz = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0};
    if (l == r) return ;
    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 val)
{
    if (tr[u].l >= l && tr[u].r <= r) tr[u].laz = val;
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, val);
        if (r > mid) modify(u << 1 | 1, l, r, val);
    }
}
///这里用last记录上一次出现的颜色,判断当前这一次跟上一次出现的颜色是否相同来判断有没有连续的。
void query(int u)
{
    if (last != tr[u].laz) cnt[tr[u].laz] ++;
    if (tr[u].laz || tr[u].l == tr[u].r) {last = tr[u].laz; return ;}
    query(u << 1), query(u << 1 | 1);
}

int main()
{
    while (~scanf("%d", &n))
    {
        memset(tr, 0, sizeof tr);
        memset(cnt, 0, sizeof cnt);
        last = 0;
        build(1, 1, 8010);

        for (int i = 0; i < n; i++)
        {
            int l, r, c; cin >> l >> r >> c;
            ///这里说明一下,首先区间是[l+1,r],这里不用说,区间最小都是1,那么我们颜色也设置成c+1,最小也是1,所以我们在build的时候巴颜色初始设置为0。
            modify(1, l + 1, r, c + 1);
        }

        query(1);

        for (int i = 1; i <= 8010; i ++)
            if (cnt[i]) cout << i - 1 << ' ' << cnt[i] << endl;
        cout << endl;
    }

    return 0;
}

6.Balanced Lineup(POJ - 3264)

题目链接: https://vjudge.net/problem/POJ-3264
题意:一共有n头牛站成一排,你可以选择某个区间内的牛,求出这个区间内的牛的最高高度和最低高度的差值,告诉了你每头牛的高度。
思路:这个题和区间最大公约数这个有点类似。这个题我们读完会发现没有修改的这一部分,只有在询问的时候会有一点麻烦,询问的时候我们分了三种情况:1.所要搜寻的范围在mid的左边。2.所要搜寻的范围在mid的右边。3.所要搜寻的范围很横跨当前的区间。分为上面所说的三种情况。(这里我有重新思考了一下pushup的意思和在什么时候使用,大家也可以再想一下)。
AC代码:

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 50010;

struct node
{
    int l, r;
    int max_h, min_h;
}tr[N << 2];
int n, m;
int a[N];

void pushup(node &u, node &l, node &r)
{
    u.max_h = max(l.max_h, r.max_h);
    u.min_h = min(l.min_h, r.min_h);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

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

node query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u];
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        if (r <= mid) return query(u << 1, l, r);
        if (l > mid) return query(u << 1 | 1, l, r);
        node left = query(u << 1, l, r);
        node right = query(u << 1 | 1, l, r);
        node res;
        pushup(res, left, right);
        return res;
    }
}

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 l, r; scanf("%d%d", &l, &r);
        node res = query(1, l, r);
        printf("%d\n", (res.max_h - res.min_h));
    }

    return 0;
}

7.Can you answer these queries?(HDU - 4027)

题目链接:https://vjudge.net/problem/HDU-4027
题意:n个数,m条指令,指令有两种:1.0 L R,表示巴序列中[L, R]的每个数开根号(向下取整)。2.1 L R,表示获取[L, R]序列中所有整数的和。自行交换L和R的大小。
思路:这个题难点在区间修改这一部分,它要让区间内的每个数都开根号,那么我们想这么几种情况,如果这个区间内的所有数都是1,那么是不是就不用更改了。如果到达了叶子节点,我们是不是就该开根号了。然后在左右子树进行递归。所以就两种情况,要么都是1,要么到叶子节点。
AC代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>

using namespace std;

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

int n, m;
ll a[N];
struct node
{
    int l, r;
    ll sum;
}tr[N << 2];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(node &u)
{
    u.sum = sqrt(u.sum);
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, a[l]};
    if (l == r) 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)
{
    if (tr[u].sum == tr[u].r - tr[u].l + 1) return ;
    if (tr[u].l == tr[u].r) {pushdown(tr[u]); return ;}

    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(u << 1, l, r);
    if (r > mid) modify(u << 1 | 1, l, r);
    pushup(u);
}

ll query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
    else
    {
        int mid = tr[u].l + tr[u].r >> 1;
        ll sum = 0;
        if (l <= mid) sum = query(u << 1, l, r);
        if (r > mid) sum += query(u << 1 | 1, l, r);
        return sum;
    }
}

int main()
{
    int t = 1;
    while (~scanf("%d", &n))
    {
        for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
        build(1, 1, n);

        printf("Case #%d:\n", t ++);
        scanf("%d", &m);
        while (m --)
        {
            int op, l, r; scanf("%d%d%d", &op, &l, &r);
            if (l > r) swap(l, r);

            if (op == 0) modify(1, l, r);
            else if (op == 1) printf("%lld\n", query(1, l, r));
        }
        puts("");
    }

    return 0;
}

8.Tunnel Warfare(HDU - 1540)

题目链接:https://vjudge.net/problem/HDU-1540
题意:有一个村落排成一排,一共n个,现在有三种操作:1.D x:第x个村庄被毁。2.Q x:询问第x个村庄与其直接或间接相关的村庄数量。3.R:最后毁坏的村庄被重建。
思路:这个题和ACcWing245 你能回答这些问题吗这个题很相似,这个题是问你直接或间接有关系的村庄的数量,其实就是问当前这个位置的村庄前面有多少个,后面有多少个,为什么说和那个题很相似呢,因为都是求前缀和和后缀和,仔细想想就明白。那么对于每个节点我们需要维护的信息有左右儿子,以及以这个节点为止的后缀和和以及以这个点开始的前缀和:

struct node
{
    int l, r;
    int lmax, rmax;
}tr[N << 2];

来看一下pushup函数,我感觉上面的(你能回答这些问题吗这个题)这个题中的pushup写得不够清楚,下面这个方式是看到另外一个博主的写法,感觉写得很清楚。我们先看某个节点的最大前缀和,它存在两种情况,首先它可能是它的左儿子的最大前缀和,那么假如它的最大前缀和恰好等于它的区间长度,那么当前节点的最大前缀和可能还会包含它的右儿子,所以可能还会加上右儿子的最大前缀和,那么最大后缀和也是同理:

void pushup(node &u, node &l, node &r)
{
    u.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
    u.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

我们这里涉及到修改,如果摧毁,就变为0,如果是复原,就变为1。

void modify(int u, int a, int c)
{
    if (tr[u].l == tr[u].r) {tr[u].lmax = tr[u].rmax = c; return ;}
    int mid = tr[u].l + tr[u].r >> 1;
    if (a <= mid) modify(u << 1, a, c);
    else modify(u << 1 | 1, a, c);
    pushup(u);
}

难点来了,查询函数。首先这是个单点查询,如果查询到了叶子节点,这个时候无论我们返回前缀还是后缀都是一样的。假如不是根节点,如果索要查询的点在当前节点的左边,那么我们需要去左儿子哪里看,但是这里又有两种情况,如果查询点在当前节点的左儿子的rmax的内部,则结果为左儿子的rmax+右儿子的lmax,否则进行递归。

int query(int u, int a)
{
    if (tr[u].l == tr[u].r) return tr[u].lmax;

    int mid = tr[u].l + tr[u].r >> 1;
    if (a <= mid)
    {
        node &op = tr[u << 1];
        if (a >= op.r - op.rmax + 1) return op.rmax + tr[u << 1 | 1].lmax;
        return query(u << 1, a);
    }
    else
    {
        node &op = tr[u << 1 | 1];
        if (a <= op.l + op.lmax - 1) return op.lmax + tr[u << 1].rmax;
        return query(u << 1 | 1, a);
    }
}

赋个图:
在这里插入图片描述
AC代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 50010;

int n, m;
struct node
{
    int l, r;
    int lmax, rmax;
}tr[N << 2];

void pushup(node &u, node &l, node &r)
{
    u.lmax = l.lmax + (l.lmax == l.r - l.l + 1 ? r.lmax : 0);
    u.rmax = r.rmax + (r.rmax == r.r - r.l + 1 ? l.rmax : 0);
}

void pushup(int u)
{
    pushup(tr[u], tr[u << 1], tr[u << 1 | 1]);
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 1, 1};
    if (l == r) 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 a, int c)
{
    if (tr[u].l == tr[u].r) {tr[u].lmax = tr[u].rmax = c; return ;}
    int mid = tr[u].l + tr[u].r >> 1;
    if (a <= mid) modify(u << 1, a, c);
    else modify(u << 1 | 1, a, c);
    pushup(u);
}

int query(int u, int a)
{
    if (tr[u].l == tr[u].r) return tr[u].lmax;

    int mid = tr[u].l + tr[u].r >> 1;
    if (a <= mid)
    {
        node &op = tr[u << 1];
        if (a >= op.r - op.rmax + 1) return op.rmax + tr[u << 1 | 1].lmax;
        return query(u << 1, a);
    }
    else
    {
        node &op = tr[u << 1 | 1];
        if (a <= op.l + op.lmax - 1) return op.lmax + tr[u << 1].rmax;
        return query(u << 1 | 1, a);
    }
}

int main()
{
    while (~scanf("%d%d", &n, &m))
    {
        stack<int> st;
        build(1, 1, n);

        while (m --)
        {
            char op[2];
            int x;
            scanf("%s", op);
            if (*op == 'D') {cin >> x; modify(1, x, 0); st.push(x);}
            else if (*op == 'Q') {cin >> x; cout << query(1, x) << endl;}
            else {modify(1, st.top(), 1); st.pop();}
        }
    }

    return 0;
}

9.覆盖的面积(HDU - 1255)

链接
题意:给定平面上若干矩形,求出被这些矩形覆盖过至少两次的区域i面积。
思路:这个题和亚特兰蒂斯这个题是一样的,主要区别就是我们在找某个区间内被覆盖过一次两次的长度,结构体的定义是:

struct node
{
    int l, r;
    int cnt;
    int len1, len2;
}tr[N * 8];

len1表示覆盖过一次的,len2表示覆盖过两次的。现在我们来看一下pushup函数,在pushup中我们要更新len1和len2,对于len1的维护和亚特兰蒂斯一样,主要是len2的维护,1.如果当前区间的cnt大于等于2,那么我们的len2直接就是区间的长度,那么如果当前区间只出现过一次,就是cnt == 1的情况,区间长度就是它的左右儿子区间的len1之和。
AC代码

#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

struct Segment
{
    double x, y1, y2;
    int k;
    bool operator < (const Segment &t) const { return x < t.x; }
}tr[N * 2];

struct node
{
    int l, r;
    int cnt;
    int len1, len2;
}tr[N * 8];

int n;
vector<double> ys;

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

void pushup(int u)
{
    if (tr[u].cnt) tr[u].len1 = ys[tr[u].r + 1] - ys[tr[u].l];
    else if (tr[u].l == tr[u].r) tr[u].len1 = 0;
    else tr[u].len1 = tr[u << 1].len1 + tr[u << 1 | 1].len1;

    if (tr[u].cnt >= 2) tr[u].len2 = ys[tr[u].r + 1] - ys[tr[u].l];
    else if (tr[u].l == tr[u].r) tr[u].len2 = 0;
    else if (tr[u].cnt == 1) tr[u].len2 = tr[u << 1].len1 + tr[u << 1 | 1].len1;
    else tr[u].len2 = tr[u << 1].len2 + tr[u << 1 | 1].len2;
}

10.无聊的数列

链接
题意:维护一个数列a,有两种操作,1 l r K D:给出一个长度等于 r-l+1 的等差数列,首项为 K,公差为 D,并将它对应加到 [l,r] 范围中的每一个数上。2 p:询问序列的第 p 个数的值 a_p 。
思路:差分+线段树。对于一个等差数列,我们要一次把它加到一个数组上,如下:
原数组:0、0、0、0、0
差分数组:0、0、0、0、0
等差数列:1、3、5、7、9
合并后数组:1、3、5、7、9、0
新差分数组:1、2、2、2、、-9
观察一下,我们发现就是分了三步:1.第一个数加上首项。2.出去首项和尾项其他数加上公差。3.尾项减去最后一个数。那么我们维护的就是差分和公差。

struct node
{
    int l, r;
    int sum;
    int tag;
}tr[N << 2];

AC代码:

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 1e5 + 10;

int n, m;
int a[N];

struct node
{
    int l, r;
    int sum;
    int tag;
}tr[N << 2];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(node &u, int val)
{
    u.sum += val * (u.r - u.l + 1);
    u.tag += val;
}

void pushdown(int u)
{
    if (tr[u].tag)
    {
        pushdown(tr[u << 1], tr[u].tag), pushdown(tr[u << 1 | 1], tr[u].tag);
        tr[u].tag = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 0};
    if (l == r) return ;
    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 val)
{
    if (tr[u].l >= l && tr[u].r <= r) {pushdown(tr[u], val); return ;}

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(u << 1, l, r, val);
    if (r > mid) modify(u << 1 | 1, l, r, val);
    pushup(u);
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1, sum = 0;
    if (l <= mid) sum = query(u << 1, l, r);
    if (r > mid) sum += query(u << 1 | 1, l, r);
    return sum;
}

signed main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    build(1, 1, n);

    while (m --)
    {
        int op; cin >> op;
        if (op == 1)
        {
            int l, r, k, d; cin >> l >> r >> k >> d;
            modify(1, l, l, k);
            if (l != r) modify(1, l + 1, r, d);
            if (r + 1 <= n) modify(1, r + 1, r + 1, -(r - l) * d - k);
        }
        else
        {
            int x; cin >> x;
            cout << query(1, 1, x) + a[x] << endl;
        }
    }

    return 0;
}

洛谷

1.P2357 守墓人

链接
题意:你可以做四个操作:1.将[l, r]这个区间所有的墓碑的风水值增加k。2.将主墓碑风水增加k。3.将主墓碑风水值减少k。4.统计[l, r]这个区间所有墓碑的风水值之和。5.求主墓碑的风水值。
思路:就是线段树模板,求主墓碑的风水值需要注意一下就可以了。

#include <bits/stdc++.h>
#define ul u << 1
#define ur u << 1 | 1

using namespace std;

const int N = 2e5 + 10;
typedef long long ll;

int n, f;
ll a[N];

struct node
{
    int l, r;
    ll sum;
    int add;
}tr[N << 2];

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u)
{
    if (tr[u].add)
    {
        tr[ul].add += tr[u].add, tr[ul].sum += (ll)(tr[ul].r - tr[ul].l + 1) * tr[u].add;
        tr[ur].add += tr[u].add, tr[ur].sum += (ll)(tr[ur].r - tr[ur].l + 1) * tr[u].add;
        tr[u].add = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, a[r], 0};
    if (l == r) return ;
    int mid = l + r >> 1;
    build(ul, l, mid), build(ur, mid + 1, r);
    pushup(u);
}

void modify(int u, int l, int r, int k)
{
    if (tr[u].l >= l && tr[u].r <= r) {tr[u].add += k; tr[u].sum += (ll)(tr[u].r - tr[u].l + 1) * k; }
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(ul, l, r, k);
        if(r > mid) modify(ur, l, r, k);
        pushup(u);
    }
}

ll query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    ll sum = 0;
    if (l <= mid) sum = query(ul, l, r);
    if (r > mid) sum += query(ur, l, r);
    return sum;
}

int main()
{
    cin >> n >> f;
    for (int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);

    int op;
    int l, r, k;
    while (f --)
    {
        cin >> op;
        if (op == 1) {cin >> l >> r >> k; modify(1, l, r, k);}
        else if (op == 2) {cin >> k; modify(1, 1, 1, k); }
        else if (op == 3) {cin >> k; modify(1, 1, 1, -k); }
        else if (op == 4) {cin >> l >> r; cout << query(1, l, r) << endl;}
        else cout << tr[1].sum - query(1, 2, n) << endl;
    }

    return 0;
}

2.P2003 [CRCI2007-2008] PLATFORME 平板

链接
题意:在这里插入图片描述
给了你每块平板的坐标和高度,然后题目中说了每个支柱都距它支撑的平板的边缘半个单位(如图)。任意一平板的两端必需有支柱或者它在另一块平板上。让你求所有木板的支柱的总长度。
题意:离散化+线段树。需要离散化的原因是每个支柱的坐标是小数。首先我们的线段树肯定是维护某个区间内的木板的支柱的高度,现在是要知道某个区间的支柱的高度。如果我们将木板的高度从低到高的顺序来排,这样一来就是一个区间修改的线段树了,我们每次就只需要更新这个区间内的木板的高度就可以了。这里我们不需要pushup,没有需要向上维护的东西。然后就是离散化过程,每次输入一个坐标,左边的坐标还要添加一个x+0.5,右边的坐标还要添加一个x-0.5,然后就是离散化的模板了。然后我们从小到大遍历每一块木板就可以了。
这里特别说一下查询过程,我们查询是找的某一个区间的高度,其实应该这么说,我们要找的是一块木板的左边支柱和右边支柱的高度,我们在传到函数里面的是支柱的坐标:

int query(int u, int l, int r, int x)
{
    if (l == r) return tr[u].val;
    int mid = l + r >> 1;
    pushdown(u);
    if (x <= mid) query(u << 1, l, mid, x);
    else query(u << 1 | 1, mid + 1, r, x);
}

AC代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 110;
typedef long long ll;

int n;
vector<double> ys;

struct Wood
{
    int x1, x2;
    int h;

    bool operator < (const Wood &t) const
    {
        return h < t.h;
    }
}wood[N];

struct node
{
    int l, r;
    int val , add;
}tr[N * 16];

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

void pushdown(int u)
{
    if (tr[u].add)
    {
        tr[u << 1].add = tr[u << 1 | 1].add = tr[u].add;
        tr[u << 1].val = tr[u << 1 | 1].val = tr[u].add;
        tr[u].add = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 0};
    if (l == r) return ;
    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 c)
{
    if (tr[u].l >= l && tr[u].r <= r) {tr[u].val = c; tr[u].add = c;}
    else
    {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, c);
        if (r > mid) modify(u << 1 | 1, l, r, c);
    }
}

int query(int u, int l, int r, int x)
{
    if (l == r) return tr[u].val;
    int mid = l + r >> 1;
    pushdown(u);
    if (x <= mid) query(u << 1, l, mid, x);
    else query(u << 1 | 1, mid + 1, r, x);
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        int x1, x2;
        int h;
        scanf("%d%d%d", &h, &x1, &x2);
        wood[i] = {x1, x2, h};
        ys.push_back(x1), ys.push_back(x1 + 0.5);
        ys.push_back(x2), ys.push_back(x2 - 0.5);
    }

    sort(ys.begin(), ys.end());
    ys.erase(unique(ys.begin(), ys.end()), ys.end());
    sort(wood + 1, wood + n + 1);

    build(1, 1, ys.size());

    ll ans = 0;

    for (int i = 1; i <= n; i++)
    {
        int lh = query(1, 1, ys.size(), find(wood[i].x1 + 0.5));
        int rh = query(1, 1, ys.size(), find(wood[i].x2 - 0.5));

        ans += wood[i].h - lh, ans += wood[i].h - rh;
        modify(1, find(wood[i].x1), find(wood[i].x2), wood[i].h);
    }

    printf("%lld\n", ans);

    return 0;
}

3.P3870 [TJOI2009] 开关

链接
题意:n盏灯排成一排,执行m次操作:1.指定一个区间[a, b]然后改变编号在这个区间内的灯的状态(把开着的关了,关着的打开)。2.指定一个区间[a, b],要求输出这个区间内有多少盏灯是打开的。
思路:首先一盏灯执行的操作数是偶数,那么它的开关状态是不变的,如果是奇数次操作,那么它的开关状态与原来相反。那么对于每一个区间,我们只要知道这个区间内的灯的操作次数,就可以确定这些灯的开关状态。那么我们怎么确定一个区间内的开着的灯的数量和关着的灯的数量呢?我们分别用一个变量来记录,如果操作次数是奇数次,就交换两个值。
AC代码:

#include <bits/stdc++.h>

#define ul u << 1
#define ur u << 1 | 1

using namespace std;

const int N = 1e5 + 10;
int n, m;
int c, a, b;

struct node
{
    int l, r;
    int cnt_1, cnt_0;
    int laz;
}tr[N << 2];

void pushup(int u)
{
    tr[u].cnt_1 = tr[ul].cnt_1 + tr[ur].cnt_1;
    tr[u].cnt_0 = tr[ul].cnt_0 + tr[ur].cnt_0;
}

void pushdown(int u)
{
    if (tr[u].laz % 2)
    {
        swap(tr[ul].cnt_0, tr[ul].cnt_1);
        swap(tr[ur].cnt_0, tr[ur].cnt_1);
        tr[ul].laz += tr[u].laz;
        tr[ur].laz += tr[u].laz;
        tr[u].laz = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, r - l + 1, 0};
    if (l == r) return ;
    int mid = l + r >> 1;
    build(ul, l, mid), build(ur, mid + 1, r);
    pushup(u);
}

void modify(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        swap(tr[u].cnt_0, tr[u].cnt_1);
        tr[u].laz ++;
        return ;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(ul, l, r);
    if (r > mid) modify(ur, l, r);
    pushup(u);
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].cnt_1;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    int sum = 0;
    if (l <= mid) sum = query(ul, l, r);
    if (r > mid) sum += query(ur, l, r);
    return sum;
}

int main()
{
    scanf("%d%d", &n, &m);
    build(1, 1, n);

    while (m --)
    {
        scanf("%d%d%d", &c, &a, &b);
        if (c == 0) modify(1, a, b);
        else printf("%d\n", query(1, a, b));
    }

    return 0;
}

4.P6492 [COCI2010-2011#6] STEP

链接
题意:给定一个长度为n的字符序列a,初始时序列中全部都是字符L,有q次修改,每次给定一个x,若a[x]为L,则将其修改成R,否则修改成L。对于一个只含字符L、R的字符串s,若其中不存在连续的L和R,则称s满足要求,每次修改后,请输出当前序列a中最长的满足要求的连续子串的长度。
思路:首先说到连续子串,我们应该就会想到最大前缀和和最大后缀和,这里的最大前缀和指的是:以当前点为起点,满足条件的最长的序列,后缀和也是同样的意思。那么这个时候我们的每个节点维护了五个信息,左右儿子,最大前缀后缀和,以及满足条件的最长序列,我们还需要两个东西,就是当前区间的第一个字符和最后一个字符,后面我们在说为什么。这个题我们可以看成01字符串。

struct node
{
    int l, r;
    int lval, rval;
    int lmax, rmax;
    int ans;
}tr[N << 2];

现在我们来看一下pushup怎么写,我们在更新lmax和rmax的时候肯定是等于左右儿子的最大lmax和rmax,但是还有一种情况就是左儿子的最右边的字符和右儿子最左边的字符是不同的,那么这个时候我们的ans就等于左右儿子的lmax+rmax了,所以我们会需要要lval和rval。
我们再来看一下当前节点的lmax和rmax怎么更新,我们看一下lmax怎么更新,rmax更新方法一样。如果当前节点的左儿子的rmax等于儿子的区间长度并且左儿子的最后一个字符不等于右儿子最开始的字符,那么这个时候当前节点的lmax就等于若儿子的rmax加上右儿子的lmax,所以pushup的函数就是:

void pushup(int u)
{
    if (tr[ul].rval ^ tr[ur].lval)
    {
        tr[u].ans = tr[ul].rmax + tr[ur].lmax;
        tr[u].ans = max(tr[u].ans, tr[ul].ans);
        tr[u].ans = max(tr[u].ans, tr[ur].ans);
    }
    else tr[u].ans = max(tr[ul].ans, tr[ur].ans);

    tr[u].lval = tr[ul].lval;
    tr[u].rval = tr[ur].rval;

    if (tr[ul].rmax == tr[ul].r - tr[ul].l + 1 && tr[ul].rval ^ tr[ur].lval) tr[u].lmax = tr[ul].rmax + tr[ur].lmax;
    else tr[u].lmax = tr[ul].lmax;

    if (tr[ur].lmax == tr[ur].r - tr[ur].l + 1 && tr[ul].rval ^ tr[ur].lval) tr[u].rmax = tr[ur].lmax + tr[ul].rmax;
    else tr[u].rmax = tr[ur].rmax;
}

我们在建树的时候,如果遇到了叶子节点,我们需要更新信息,ans = lmax = rmax = 1并且最后一个字符和第一个字符都是0,这里是初始化。我们在修改操作的时候也有类似的操作,如果遇到了根节点,ans = lmax = rmax = 1,然后我们需要修改字符对吧,这个时候我们需要把第一个字符和最后一个字符取反就可以了。具体看代码。
AC代码:

#include <bits/stdc++.h>

#define ul u << 1
#define ur u << 1 | 1

using namespace std;

const int N = 2e5 + 10;

int n, m;
struct node
{
    int l, r;
    int lval, rval;
    int lmax, rmax;
    int ans;
}tr[N << 2];

void pushup(int u)
{
    if (tr[ul].rval ^ tr[ur].lval)
    {
        tr[u].ans = tr[ul].rmax + tr[ur].lmax;
        tr[u].ans = max(tr[u].ans, tr[ul].ans);
        tr[u].ans = max(tr[u].ans, tr[ur].ans);
    }
    else tr[u].ans = max(tr[ul].ans, tr[ur].ans);

    tr[u].lval = tr[ul].lval;
    tr[u].rval = tr[ur].rval;

    if (tr[ul].rmax == tr[ul].r - tr[ul].l + 1 && tr[ul].rval ^ tr[ur].lval) tr[u].lmax = tr[ul].rmax + tr[ur].lmax;
    else tr[u].lmax = tr[ul].lmax;

    if (tr[ur].lmax == tr[ur].r - tr[ur].l + 1 && tr[ul].rval ^ tr[ur].lval) tr[u].rmax = tr[ur].lmax + tr[ul].rmax;
    else tr[u].rmax = tr[ur].rmax;
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if (l == r)
    {
        tr[u].ans = tr[u].lmax = tr[u].rmax = 1;
        tr[u].lval = tr[u].rval = 0;
        return ;
    }
    int mid = l + r >> 1;
    build(ul, l, mid), build(ur, mid + 1, r);
    pushup(u);
}

void modify(int u, int l, int r, int x)
{
    if (l == r)
    {
        tr[u].ans = tr[u].lmax = tr[u].rmax = 1;
        tr[u].lval = tr[u].rval = !tr[u].lval;
        return ;
    }

    int mid = l + r >> 1;
    if (x <= mid) modify(ul, l, mid, x);
    else modify(ur, mid + 1, r, x);
    pushup(u);
}

int main()
{
    scanf("%d%d", &n, &m);
    build(1, 1, n);

    int x;
    while (m --)
    {
        scanf("%d", &x);
        modify(1, 1, n, x);
        printf("%d\n", tr[1].ans);
    }

    return 0;
}

5.P3801 红色的幻想乡

链接
题意:给你一个n×m的方格,你可以执行两种操作,1. 1 x y:从(x, y)坐标为起点,向它的四周染色,而起点不会被染色,如果是已经染了色,那么颜色会消失。2. 2 x1 y1 x2 y2,询问左上点位(x1, y1),右下点位(x2, y2)的矩形范围内,被染色的地区的数量。
思路:首先这是一个二维的,我们可以用一个二维的线段树来维护,但是我不会,就只会一种比较暴力的线段树方法,就是对于x轴和y轴单独来建设线段树,线段树的意义是:这个区间内有多上行(列)执行了操作,我们用0表示未染色,1表示染色了。所以我们的线段树结构体该这样创建:

///线段树维护的是某个区间内有多少行\列被操作。
struct node
{
    int l, r;
    int sum;
}t1[N << 2], t2[N << 2];

现在我们的结构体所要维护的东西建好了,现在来想一下修改数组,我们是要修改某个范围内要被修改的行(列),那么当我们到达根节点的时候才会修改对吧,所以我们只需要用一个^来改变就可以了。(我们在修改的时候其实是一个单点修改)看代码:

void modify_1(int u, int l, int r)
{
    if (t1[u].l == t1[u].r) {t1[u].sum ^= 1; return ;}
    int mid = t1[u].l + t1[u].r >> 1;
    if (l <= mid) modify_1(ul, l, r);
    else modify_1(ur, l, r);
    pushup_1(u);
}

AC代码:

#include <bits/stdc++.h>

#define int long long

#define ul u << 1
#define ur u << 1 | 1

using namespace std;

const int N = 1e5 + 10;

int n, m, q;
///线段树维护的是某个区间内有多少行\列被操作。
struct node
{
    int l, r;
    int sum;
}t1[N << 2], t2[N << 2];

void pushup_1(int u)
{
    t1[u].sum = t1[ul].sum + t1[ur].sum;
}

void pushup_2(int u)
{
    t2[u].sum = t2[ul].sum + t2[ur].sum;
}

void build_1(int u, int l, int r)
{
    t1[u] = {l, r, 0};
    if (l == r) return ;
    int mid = l + r >> 1;
    build_1(ul, l, mid), build_1(ur, mid + 1, r);
    pushup_1(u);
}

void build_2(int u, int l, int r)
{
    t2[u] = {l, r, 0};
    if (l == r) return ;
    int mid = l + r >> 1;
    build_2(ul, l, mid), build_2(ur, mid + 1, r);
    pushup_2(u);
}

void modify_1(int u, int l, int r)
{
    if (t1[u].l == t1[u].r) {t1[u].sum ^= 1; return ;}
    int mid = t1[u].l + t1[u].r >> 1;
    if (l <= mid) modify_1(ul, l, r);
    else modify_1(ur, l, r);
    pushup_1(u);
}

void modify_2(int u, int l, int r)
{
    if (t2[u].l == t2[u].r) {t2[u].sum ^= 1; return ;}
    int mid = t2[u].l + t2[u].r >> 1;
    if (l <= mid) modify_2(ul, l, r);
    else modify_2(ur, l, r);
    pushup_2(u);
}

///多少列被修改过
int query_1(int u, int l, int r)
{
    if (t1[u].l >= l && t1[u].r <= r) return t1[u].sum;
    int mid = t1[u].l + t1[u].r >> 1, sum = 0;
    if (l <= mid) sum = query_1(ul, l, r);
    if (r > mid) sum += query_1(ur, l, r);
    return sum;
}

///多少行被修改过
int query_2(int u, int l, int r)
{
    if (t2[u].l >= l && t2[u].r <= r) return t2[u].sum;
    int mid = t2[u].l + t2[u].r >> 1, sum = 0;
    if (l <= mid) sum = query_2(ul, l, r);
    if (r > mid) sum += query_2(ur, l, r);
    return sum;
}

signed main()
{
    scanf("%lld%lld%lld", &n, &m, &q);
    build_1(1, 1, n), build_2(1, 1, m);

    int op;
    while (q --)
    {
        scanf("%lld", &op);
        if (op == 1)
        {
            int x, y; scanf("%lld%lld", &x, &y);
            modify_1(1, x, x);
            modify_2(1, y, y);
        }
        else
        {
            int x1, y1, x2, y2; scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2);
            int a1 = query_1(1, x1, x2);
            int a2 = query_2(1, y1, y2);
            int ans = a1 * (long long)(y2 - y1 + 1) + a2 * (long long)(x2 - x1 + 1) - (long long)(a1 * a2 << 1);
            printf("%lld\n", ans);
        }
    }

    return 0;
}

6.P6327 区间加区间sin和

链接
题意:给出一个长度为n的整数序列,进行m次操作,操作分两种:1.给出l,r,v,将[l, r]中的所有数加上v。2.给出[l, r],询问sin(a[i])的和。
思路:sin(a+b) = sina cosb + cosa sinb,cos(a+b) = cosa cosb - sina sinb。首先我们要知道这两个公式,因为在求sin(a+b)的时候出现了cos,所以我们需要知道cos的和公式。那么对于某个区间来说,我们是不是应该要存储它的sin和以及cos,还要存储这个区间加上了多少。

///线段树维护的是这个区间内的所有数的sin和以及cos和,以及这个区间内加的数。
struct node
{
    int l, r;
    ///分别表示这个区间的sin和以及cos和
    double sink, cosk;
    ll laz;
}tr[N << 2];

我们来看一下pushdown函数,因为题中有一个操作是给[l, r]中的所有数加上一个数,所以我们需要pushdown,那么对于一个区间内的sin和以及cos和我们就直接用公式更新就可以了,那么我们来看一下pushdown的代码:

void pushup(int u, double sinx, double cosx)
{
    double sum_sin = tr[u].sink, sum_cos = tr[u].cosk;
    tr[u].sink = sum_sin * cosx + sum_cos * sinx;
    tr[u].cosk = sum_cos * cosx - sum_sin * sinx;
}

void pushdown(int u)
{
    if (tr[u].laz)
    {
        tr[ul].laz += tr[u].laz;
        tr[ur].laz += tr[u].laz;
        double sinx = sin(tr[u].laz), cosx = cos(tr[u].laz);
        pushup(ul, sinx, cosx);
        pushup(ur, sinx, cosx);
        tr[u].laz = 0;
    }
}

接着我们看一下修改函数,首先我们是要将一个区间上的所有数都加上一个数,那么加上这个数我们会更新它的sin和以及cos和,这是在遇到根节点的时候,其实说白了就是一般的更新方式,没有什么特别的地方:

void modify(int u, int l, int r, double sinx, double cosx, int x)
{
    if (tr[u].l >= l && tr[u].r <= r) {pushup(u, sinx, cosx); tr[u].laz += x; return ;}
    int mid = tr[u].l + tr[u].r >> 1;
    pushdown(u);
    if (l <= mid) modify(ul, l, r, sinx, cosx, x);
    if (r > mid) modify(ur, l, r, sinx, cosx, x);
    pushup(u);
}

看AC代码:

#include <bits/stdc++.h>

#define ul u << 1
#define ur u << 1 | 1

using namespace std;

const int N = 2e5 + 10;
typedef long long ll;

int n, m;
int a[N];

///线段树维护的是这个区间内的所有数的sin和以及cos和,以及这个区间内加的数。
struct node
{
    int l, r;
    double sink, cosk;
    ll laz;
}tr[N << 2];

void pushup(int u)
{
    tr[u].cosk = tr[ul].cosk + tr[ur].cosk;
    tr[u].sink = tr[ul].sink + tr[ur].sink;
}

void pushup(int u, double sinx, double cosx)
{
    double sum_sin = tr[u].sink, sum_cos = tr[u].cosk;
    tr[u].sink = sum_sin * cosx + sum_cos * sinx;
    tr[u].cosk = sum_cos * cosx - sum_sin * sinx;
}

void pushdown(int u)
{
    if (tr[u].laz)
    {
        tr[ul].laz += tr[u].laz;
        tr[ur].laz += tr[u].laz;
        double sinx = sin(tr[u].laz), cosx = cos(tr[u].laz);
        pushup(ul, sinx, cosx);
        pushup(ur, sinx, cosx);
        tr[u].laz = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if (l == r) {tr[u].sink = sin(a[l]); tr[u].cosk = cos(a[l]); return ;}
    int mid = l + r >> 1;
    build(ul, l, mid), build(ur, mid + 1, r);
    pushup(u);
}

void modify(int u, int l, int r, int x)
{
    if (tr[u].l >= l && tr[u].r <= r) {pushup(u, sin(x), cos(x)); tr[u].laz += x; return ;}
    int mid = tr[u].l + tr[u].r >> 1;
    pushdown(u);
    if (l <= mid) modify(ul, l, r, x);
    if (r > mid) modify(ur, l, r, x);
    pushup(u);
}

double query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].sink;
    if (l > tr[u].r || tr[u].l > r) return 0;
    int mid = tr[u].l + tr[u].r >> 1;
    pushdown(u);
    return query(ul, l, r) + query(ur, l, r);
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    build(1, 1, n);

    scanf("%d", &m);
    int op;
    int l, r, v;
    while (m --)
    {
        scanf("%d", &op);
        if (op == 1)
        {
            scanf("%d%d%d", &l, &r, &v);
            modify(1, l, r, v);
        }
        else
        {
            scanf("%d%d", &l, &r);
            printf("%.1f\n", query(1, l, r));
        }
    }

    return 0;
}

7.P1904 天际线

链接
题意:
思路:我们需要记录的是每个单元的建筑物的最大值,那么我们直接用一个线段树来维护某个区间内的最大值即可。因为题中给的是一个区间的建筑物,所以我们可以在过程中更新高度,我们这里需要两种高度,第一个是当前的高度,第二个是更新的高度,那么结构体就是:

///求每个区间的建筑物高度的最大值。
struct node
{
    int l, r;
    int len;
    int cnt;
}tr[N << 2];

这里我们需要pushdown,在pushdown 的过程中,我们需要更新儿子区间的高度,以及更新的高度:

void pushdown(int u)
{
    if (tr[u].cnt)
    {
        tr[ul].cnt = tr[u].cnt;
        tr[ur].cnt = tr[u].cnt;
        tr[ul].len = tr[ul].cnt;
        tr[ur].len = tr[ur].cnt;
        tr[u].cnt = 0;
    }
}

AC代码:

#include <bits/stdc++.h>

#define ul u << 1
#define ur u << 1 | 1

using namespace std;

const int N = 10005;

///求每个区间的建筑的最大值。
struct node
{
    int l, r;
    int len;
    int cnt;
}tr[N << 2];

struct Segment
{
    int l, r;
    int h;
    bool operator < (const Segment &t) const
    {
        ///高度从小到达,后面直接对高度赋值
        return h < t.h;
    }
}seg[N];

int a[N];

void pushdown(int u)
{
    if (tr[u].cnt)
    {
        tr[ul].cnt = tr[u].cnt;
        tr[ur].cnt = tr[u].cnt;
        tr[ul].len = tr[ul].cnt;
        tr[ur].len = tr[ur].cnt;
        tr[u].cnt = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 0};
    if (tr[u].l == tr[u].r) return ;
    int mid = l + r >> 1;
    build(ul, l, mid), build(ur, mid + 1, r);
}

void modify(int u, int l, int r, int c)
{
    if (tr[u].l >= l && tr[u].r <= r)
    {
        tr[u].len = c;
        tr[u].cnt = c;
        return ;
    }

    pushdown(u);

    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) modify(ul, l, r, c);
    if (r > mid) modify(ur, l, r, c);

    tr[u].len = max(tr[ul].len, tr[ur].len);
}

int query(int u, int l, int r)
{
    if (tr[u].l >= l && tr[u].r <= r) return tr[u].len;

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) return query(ul, l, r);
    else return query(ur, l, r);
}

int main()
{
    build(1, 1, 1000);
    int l, h, r;
    int cnt = 1;
    while (scanf("%d%d%d", &seg[cnt].l, &seg[cnt].h, &seg[cnt].r))
    {
        if (seg[cnt].l == 0 && seg[cnt].r == 0 && seg[cnt].h == 0) break;
        cnt ++;
    }

    sort(seg + 1, seg + cnt);

    for (int i = 1; i < cnt; i++)
        modify(1, seg[i].l, seg[i].r - 1, seg[i].h);

    for (int i = 1; i <= 1000; i++)
        a[i] = query(1, i, i);

    for (int i = 1; i <= 1000; i++)
        if (a[i] != a[i - 1]) printf("%d %d ", i, a[i]);

    return 0;
}


二、树状数组

ACwing

1.楼兰图腾

题意:
思路:学树状数组的第一道题,发现有一篇博客讲得很好:链接
对于题中想找到的两种团,我们只需要保存每个位置,它左右两边比它大的和比它小的就可以了。看代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 200010;
typedef long long ll;

int n;
int a[N];
int tr[N];
int Greater[N], Lower[N];

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

void add(int x, int k)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += k;
}

int sum(int x)
{
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];

///从左往右
    for (int i = 1; i <= n; i ++)
    {
        int now = a[i];
        Greater[i] = sum(n) - sum(now);
        Lower[i] = sum(now - 1);
        add(now, 1);
    }

    memset(tr,0, sizeof tr);

    ll res1 = 0, res2 = 0;
    ///从右往左
    for (int i = n; i; i --)
    {
        int now = a[i];
        ///加上右边比now大的数。
        res1 += Greater[i] * (ll)(sum(n) - sum(now));
        ///加上右边比now小的数 
        res2 += Lower[i] * (ll)(sum(now - 1));
        add(now, 1);
    }

    cout << res1 << endl << res2 << endl;

    return 0;
}

最开始的时候我没有弄懂为什么在我们寻找答案的时候还要继续add,我认为只需要在我们更新Greater和Lower的时候才需要add,我们的前面一层是从左到右,后面一层是从右到左。

2.一个简单的整数问题

题意:给定长度为 N 的数列 A,然后输入 M 行操作指令。第一类指令形如 C l r d,表示把数列中第 l∼r 个数都加 d。第二类指令形如 Q x,表示询问数列中第 x 个数的值。对于每个询问,输出一个整数表示答案。
思路:感觉树状数组和线段树还是有很大的联系的,线段树我们需要维护什么,而树状数组我们也需要只要我们的树状数组要存储的是什么,对于这个题,它是区间修改然后加单点查询,那树状数组本来就是求得前缀和,那么 我们在寻找某个点得值的时候就可以用前缀和来求了。
AC代码:

#include <bits/stdc++.h>

using namespace std;

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

int n, m;
int tr[N];
int a[N];

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

void add(int x, int k)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += k;
}

ll sum(int x)
{
    ll res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >> a[i];

    for (int i = 1; i <= n; i ++) add(i, a[i] - a[i - 1]);

    char op[2];
    int l, r, d;
    while (m --)
    {
        scanf("%s%d", op, &l);
        if (*op == 'C')
        {
            scanf("%d%d", &r, &d);
            add(l, d), add(r + 1, -d);
        }
        else printf("%lld\n", sum(l));
    }

    return 0;
}

3.一个简单的整数问题2

链接
题意:给定一个长度为 N 的数列 A,以及 M 条指令,每条指令可能是以下两种之一:C l r d,表示把 A[l],A[l+1],…,A[r] 都加上 d。Q l r,表示询问数列中第 l∼r 个数的和。对于每个询问,输出一个整数表示答案。
思路:这里跟上面的不同,这里是区间查询和区间修改。首先我们想一下,如果给你一个数组,我们是不是可以求导一个差分数组b,然后我们对差分用前缀和是不是可以求得每个位置的数,那么这里题就是利用的这个特点。
在这里插入图片描述

我们用一个二层的循环可以求的每一个位置的值,其中黑色的部分就是我们求得的每个位置的值,然后我们将它补全,我们可以发现,如果要求a[1] + a[2] + ……+ a[x]的话其实就是(b[1] + b[2] +……+b[x])(x+1)-(b[1] + 2 * b[2] + …… + xb[x]),那么我们只需要两个差分数组即可求的一个区间的值了。AC代码:

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 1e5 + 10;

int n, m;
int a[N];
int tr_1[N], tr_2[N];///(b[]是差分数组)tr_1维护b[]的前缀和,tr_2[]是b[i]*i的前缀和

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

void add(int tr[], int x, int k) { for (int i = x; i <= n; i += lowbit(i)) tr[i] += k; }

int sum(int tr[], int x)
{
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}

int prefix_sum(int x) { return sum(tr_1, x) * (x + 1) - sum(tr_2, x); }

signed main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >> a[i];

    for (int i = 1; i <= n; i ++)
    {
        int b = a[i] - a[i - 1];
        add(tr_1, i, b);
        add(tr_2, i, b * i);
    }

    while (m --)
    {
        char op[2];
        int l, r, d;
        scanf("%s%lld%lld", op, &l, &r);
        if (*op == 'Q') printf("%lld\n", prefix_sum(r) - prefix_sum(l - 1));
        else
        {
            scanf("%lld", &d);
            add(tr_1, l, d), add(tr_2, l, l * d);
            add(tr_1, r + 1, -d), add(tr_2, r + 1, (r + 1) * -d);
        }
    }

    return 0;
}

4.谜一样的牛

链接
题意:n头牛,他们的身高是从1~n,a[i]表示第i头牛的前面有a[i]头头你它矮,求每头牛的身高。
思路:首先对于第i头牛来说,它前面有a[i]头比它矮,说明它是第a[i]+1高的牛,那么我们每次在找高度的时候我们只需要找到第a[i]+1大的数,然后在把这个数删掉,后面的在在剩余的数字里面找,并且有删除的过程。我们在找第a[i]+1大的数的时候只需要一个二分就可以了。接下来说一下tr数组的含义,就是以i结尾还可以选择多少个数,最开始的时候每个数都可以选择,那么这里需要一个初始化

for (int i = 1; i <= n; i ++) tr[i] = lowbit(i);

AC代码:

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 1e5 + 10;

int n;
int h[N];
int tr[N];
int ans[N];

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

void add(int x, int k) { for (int i = x; i <= n; i += lowbit(i)) tr[i] += k; }

int sum(int x)
{
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}

signed main()
{
    cin >> n;
    for (int i = 2; i <= n; i ++) cin >> h[i];

    for (int i = 1; i <= n; i ++) tr[i] = lowbit(i);

	/*
	这里为什么是倒着来找的呢?我最开始的时候一直没有搞懂。
	因为在最开始的时候是一个数都没有被选择的,每一个书都可以选择,我们从中间的人任何一个位置开始找都是不行的,我们只有从最后一个位置开始找才是合理的。
	*/

    for (int i = n; i; i --)
    {
        int k = h[i] + 1;
        int l = 1, r = n;
        while (l < r)
        {
            int mid = l + r >> 1;
            if (sum(mid) >= k) r = mid;
            else l = mid + 1;
        }
        ans[i] = r;
        add(r, -1);
    }

    for (int i = 1; i <= n; i ++) cout << ans[i] << endl;

    return 0;
}

洛谷

1.守墓人

题意:和线段树中的是一个题,这里是树状数组的做法。
思路:这里也是区间求和和区间修改,这个题的解题思路和一个简单的整数问题2是一样的,这里就是求区间的时候有一点绕,我最开始的时候是直接用的暴力来求,毫无疑问超时了。看一下代码:

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 2e5 + 10;

int n, m;
int a[N];
int tr[N];

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

void add(int x, int k)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += k;
}

int sum(int x)
{
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}

signed main()
{
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; i ++)
    {
        scanf("%lld", &a[i]);
        add(i, a[i] - a[i - 1]);
    }

    int op, l, r, k;
    while (m --)
    {
        scanf("%lld", &op);
        if (op == 1)
        {
            scanf("%lld%lld%lld", &l, &r, &k);
            add(l, k), add(r + 1, -k);
        }
        else if (op == 2)
        {
            scanf("%lld", &k);
            tr[1] += k;
        }
        else if (op == 3)
        {
            scanf("%lld", &k);
            tr[1] -= k;
        }
        else if (op == 4)
        {
            scanf("%lld%lld", &l, &r);
            int ans = 0;
            for (int i = l; i <= r; i ++) ans += sum(i);
            printf("%lld\n", ans);
        }
        else printf("%lld\n", sum(1));
    }

    return 0;
}

正确的应该是开两个树状数组,一个用来存数组的差分,另一个是求i*数组差分。看代码:

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 2e5 + 10;

int n, m;
int a[N];
int tr_1[N], tr_2[N];

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

void add(int tr[], int x, int k)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += k;
}

int sum(int tr[], int x)
{
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}

int pre_sum(int x)
{
    return sum(tr_1, x) * (x + 1) - sum(tr_2, x);
}

signed main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        add(tr_1, i, a[i] - a[i - 1]);
        add(tr_2, i, i * (a[i] - a[i - 1]));
    }

    int op, l, r, k;
    while (m --)
    {
        cin >> op;
        if (op == 1)
        {
            cin >> l >> r >> k;
            add(tr_1, l, k), add(tr_2, l, l * k);
            add(tr_1, r + 1, -k), add(tr_2, r + 1, (r + 1) * -k);
        }
        else if (op == 2)
        {
            cin >> k;
            add(tr_1, 1, k), add(tr_2, 1, k);
            add(tr_1, 2, -k), add(tr_2, 2, 2 * -k);
        }
        else if (op == 3)
        {
            cin >> k;
            add(tr_1, 1, -k), add(tr_2, 1, -k);
            add(tr_1, 2, k), add(tr_2, 2, 2 * k);
        }
        else if (op == 4)
        {
            cin >> l >> r;
            cout << pre_sum(r) - pre_sum(l - 1) << endl;
        }
        else cout << pre_sum(1) << endl;
    }

    return 0;
}

2.逆序对

链接
题意:给你n个数,让你求逆序对的数目。
思路:首先我们想一下怎样找逆序对,树状数组又该存储什么?我们想一下,求第i个数,是不是只需要找到1–i-1中有多少个数比第i个数小,继续,我们将树状数组的初始值都设置为0,然后我们在加数,对于第i个数,我们在之前就将它之前比它小的数添加进去,树状数组的值为1代表出现过了,所以我们只需要求一个前缀和,就可以求出有多少个逆序对。

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 5e5 + 10;

int n;
int ans;
int tr[N];
int ranks[N];

struct point
{
    int num, val;
    ///按照出现的顺序排序,按照值的大小排序
    bool operator < (const point &t) const
    {
        if (val == t.val) return num < t.num;
        return val < t.val;
    }
}a[N];

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

void add(int x, int k)
{
    for (int i = x; i <= n; i += lowbit(i)) tr[i] += k;
}

int sum(int x)
{
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}

signed main()
{
    scanf("%lld", &n);
    for (int i = 1; i <= n; i ++)
    {
        scanf("%lld", &a[i].val);
        a[i].num = i;
    }

    sort(a + 1, a + 1 + n);

    for (int i = 1; i <= n; i ++) ranks[a[i].num] = i;

    for (int i = 1; i <= n; i ++)
    {
        add(ranks[i], 1);
        ans += i - sum(ranks[i]);
    }

    printf("%lld\n", ans);

    return 0;
}

这里的有几个注意点,第一就是我们要用一个结构体来存储值和出现的位置,然后根据值的大小进行排序,值小的尽量排到前面,值相同但是出现早的也尽量排在前面,然后在一次遍历就可以了。

3.P5094 [USACO04OPEN] MooFest G 加强版

链接
题意:给你n头牛,没两头牛之间可以交流,交流有个最小值是max(v[i],v[j])*dis(i,j),现在告诉了你每头牛的v[i],x[i],让你求最小的值之和。
思路:因为每头牛的位置是不固定的,那么我们按照v的大小从小到大进行排序,(经验告诉我们,有比较的题就先排序…… 按照x轴从大到小排序,这样用树状数组维护坐标就行了 对于绝对值,可以拆成正负分别计算,因此还需要维护一个区间数量)。
这里要维护两个线段树,第一个是记录当前位置之前有多少头牛的x是是比当前牛的x要小的,第二个是维护比当前位置x小的x的和,因为题目中是求max(v[i],v[j])*dis(i,j)的和,所以这里就存在一个dis(i,j)的正负问题,我们就要考虑x[i]和x[j]的大小问题:
1.当x[i]>x[j]的时候,我们只需要统计在这之前比x[i]小的个数以及这些牛的x之和。
2.当x[i]<x[j]的时候,首先我们知道了这个位置,坐标比它小的和,那么我们用一个前缀和来记录然后减去这个和就是比它大的。其他同理。
AC代码:

#include <bits/stdc++.h>

#define int long long

using namespace std;

const int N = 1000010;

int n;
int all, ans;
int tr_1[N], tr_2[N];
/*
    tr_1表示坐标小于当前位置的数量,tr_2表示坐标小于当前位置的和
*/

struct node
{
    int v, x;
    bool operator < (const node &t) const
    {
        return v < t.v;
    }
}a[N];

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

void add(int tr[], int x, int k) { for (int i = x; i <= 50000; i += lowbit(i)) tr[i] += k; }

int sum(int tr[], int x)
{
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}

signed main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i].v >> a[i].x;

    sort(a + 1, a + 1 + n);

    for (int i = 1; i <= n; i ++)
    {
        int num = sum(tr_1, a[i].x);
        int t_sum = sum(tr_2, a[i].x);

        ///x_i > x_j
        ans += (num * a[i].x - t_sum) * a[i].v;
        ///x_j > x_i
        ans += ((all - t_sum) - (i - num - 1) * a[i].x) * a[i].v;

        add(tr_1, a[i].x, 1);
        add(tr_2, a[i].x, a[i].x);

        all += a[i].x;
    }

    cout << ans << endl;

    return 0;
}

4.三元上升子序列

链接
题意:在含有 nn 个整数的序列 ​a_1,a_2,a_3……a_n 中,三个数被称作thair,求一个序列中的thair的个数。
思路:设f[i][j]为以a[j]为结尾的长度为i的上升子序列的个数。得到状态转移方程f[i][j]=∑ k<j,a[k]<a[j] f[i−1][k]。
首先我们就a[]进行离散化,然后对它创建树状数组。

#include <bits/stdc++.h>

#define int long long 

using namespace std;

const int N = 3e4 + 10;

int n, m;
int ans;
int a[N], s[N];
int tr[N];
int f[4][N];

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

void add(int x, int k) { for (int i = x; i <= m; i += lowbit(i)) tr[i] += k; }

int sum(int x)
{
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}

int find(int x){ return lower_bound(s + 1, s + m + 1, x) - s; }

signed main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i], s[i] = a[i];

    ///离散化
    sort(s + 1, s + n + 1);
    m = unique(s + 1, s + n + 1) - s - 1;

    for (int i = 1; i <= n; i ++) f[1][i] = 1, a[i] = find(a[i]);

    for (int i = 2; i <= 3; i ++)
    {
        memset(tr, 0, sizeof tr);
        for (int j = 1; j <= n; j ++)
        {
            f[i][j] = sum(a[j] - 1);
            add(a[j], f[i - 1][j]);
        }
    }

    for (int i = 1; i <= n; i ++) ans += f[3][i];

    cout << ans << endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值