线段树相关算法题(1)
hello 大家好,今天是2025年8月24日,我来给大家分享两道线段树相关的算法题。题目难度递增,由简单到中等,学过线段树的朋友们可以来挑战一下。
前言(预备知识)
1. 在解决线段树相关题目时,可以根据下面几个方面来记忆以及修改模板:
a. 根据查询以及修改操作,决定结构体中维护什么信息;
b. pushup:根据左右孩子维护的信息,更新当前结点维护的信息;
c. pushdown:当前结点的懒信息往下发一层,让左右孩子接收懒信息,并清空当前节点的懒信息
d. lazy:当前区间收到修改操作之后,更新当前结点维护的信息并且把修改信息“懒”下来;
e. build:遇到叶子结点返回,否则递归处理左右孩子,然后整合左右孩子的信息;
f. modify:遇到完全覆盖的区间,直接修改;否则有懒信息就先分给左右孩子懒信息,然后递归处 理左右区间;最后整合左右孩子的区间;
g. query:遇到完全覆盖的区间,直接返回节点维护的信息;否则有懒信息就先分给左右孩子懒信 息,然后整合左右区间的查询信息。
2. 实现时候易错的细节问题:
a. lazy 函数只把懒信息存了下来,没有修改区间维护的信息;
b. query 以及 modify 操作,没有分配懒信息;
c. pushdown 之后,没有清空当前结点的懒标记。
3. 线段树如果想要做到单次区间修改操作的时间复杂度为 log(n),那么在一段范围上执行修改 操作之后,需要能够在 O(1)时间内得到需要维护的信息。
题目一:忠诚
题目链接:忠诚
1:题目描述
分析:本题没有弯弯绕,可以当作线段树的模板题。没有修改操作,只有查询操作。
当然了,这道题目是静态问题也可以使用ST表解决,具体可以观看本专栏下的《ST表》。
2:算法原理
线段树:
step1. 结构体 node 中存储什么信息???
根据题目中的查询要求,显然我们需要维护一个区间的区间和。
因此 node 当中维护的信息为:区间左端点、区间右端点、区间和。
struct node
{int l, r, min;
}tr[N << 2];
step2. pushup 根据左右孩子维护的信息,更新当前结点维护的信息
已知左区间的最小值和右区间的最小值,就可以整合出当前区间的最小值。
void pushup(int p)
{tr[p].min = tr[lc].min + tr[rc].min;
}
step3. pushdown & lazy. 由于本题是静态问题,不存在修改操作,自然,懒标记也是不需要的。
step4. build query……基本上都是模板,不再过多赘述了。
分析好上述问题之后,就可以尝试也代码了~~
3:代码实现
#include <iostream>using namespace std;#define lc p << 1
#define rc p << 1 | 1const int N = 1e5 + 10;int n, m;
int a[N];struct node
{int l, r, min;
}tr[N << 2];void build(int p, int l, int r)
{tr[p] = {l, r, a[l]};if(l == r) return;int mid = (l + r) >> 1;build(lc, l, mid);build(rc, mid + 1, r);tr[p].min = min(tr[lc].min, tr[rc].min);
}int query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if(x <= l && r <= y) return tr[p].min;int mid = (l + r) >> 1, ret = 1e9;if(x <= mid) ret = min(ret, query(lc, x, y));if(y > mid) ret = min(ret, query(rc, x, y));return ret;
}int main()
{cin >> n >> m;for(int i = 1; i <= n; i++) cin >> a[i];build(1, 1, n);while(m--){int x, y; cin >> x >> y;cout << query(1, x, y) << " ";}return 0;
}
题目二:开关
题目链接:开关
1:题目描述
2:提炼经验(很重要)
经验:对于一个数组中只有两种状态的题目(灯的亮和灭、奇数和偶数、雄性和雌性)我们可以将其中一个状态设为1,将另一个状态设为0。以便简化题目。
就比如本题,我们要查询的是一段区间内亮着的灯的个数,我们可以把亮着的灯设为1,灭了的灯设为0,对于查询操作,就简化成了求区间和的操作,对于修改操作,就简化成了0变1,1变0的操作。
那么本题两种操作就是区间修改和区间查询。可以尝试用线段树来解决。
3:算法原理
线段树:
step1. 结构体 node 中存储什么信息???
根据题目中的查询要求和我们的问题转化,显然我们需要维护一个区间的区间和。(区间中1的个数)
因此 node 当中维护的信息为:区间左端点、区间右端点、区间和。
又因为这道题目涉及区间修改操作,因此需要懒标记来优化时间复杂度。
我们来分析,得到一个性质,对于一段区间,如果执行了偶数次修改操作,其实就相当于什么都没有做。如果执行了奇数次操作,相当于区间和由 sum 变为 len - sum。
因此在 node 当中多维护一个 cnt 信息(懒标记)表示对此区间一共执行了多少次操作。
综上所述,node 中维护的信息为:区间左端点、区间右端点、区间和、cnt信息。
struct node
{int l, r, sum, cnt;
}tr[N << 2];
step2. pushup 根据左右孩子维护的信息,更新当前结点维护的信息
已知左区间的区间和和右区间的区间和,就可以整合出当前区间的区间和。
void pushup(int p)
{tr[p].sum = tr[lc].sum + tr[rc].sum;
}
step3. pushdown & lazy. 由于本题涉及懒标记下发(区间修改),因此需要 pushdown 和 lazy 操作
pushdown 操作,我们要做的是:
1.懒标记下发给左孩子 2.懒标记下发给右孩子 3.清空当前结点的懒信息
void pushdown(int p)
{lazy(lc, tr[p].cnt);lazy(rc, tr[p].cnt);tr[p].cnt = 0;
}
lazy 操作,我们要做的是:
新来一个修改信息之后:
1.更新当前结点维护的信息 2.把修改信息“懒”下来
void lazy(int p, int cnt)
{if(cnt % 2 == 1) tr[p].sum = tr[p].r - tr[p].l + 1 - tr[p].sum;tr[p].cnt += cnt;
}
step4. build query……基本上都是模板,不再过多赘述了。
分析好上述问题之后,就可以尝试也代码了~~
4:代码实现
#include <iostream>using namespace std;#define lc p << 1
#define rc p << 1 | 1const int N = 1e5 + 10;int n, m;
struct node
{int l, r, sum, cnt;
}tr[N << 2];void lazy(int p, int cnt)
{if(cnt % 2 == 1) tr[p].sum = tr[p].r - tr[p].l + 1 - tr[p].sum;tr[p].cnt += cnt;
}void pushup(int p)
{tr[p].sum = tr[lc].sum + tr[rc].sum;
}void pushdown(int p)
{lazy(lc, tr[p].cnt);lazy(rc, tr[p].cnt);tr[p].cnt = 0;
}void build(int p, int l, int r)
{tr[p] = {l, r, 0, 0};if(l == r) return;int mid = (l + r) >> 1;build(lc, l, mid); build(rc, mid + 1, r);pushup(p);
}void modify(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if(x <= l && r <= y){lazy(p, 1);return;}pushdown(p);int mid = (l + r) >> 1;if(x <= mid) modify(lc, x, y);if(y > mid) modify(rc, x, y);pushup(p);
}int query(int p, int x, int y)
{int l = tr[p].l, r = tr[p].r;if(x <= l && r <= y) return tr[p].sum;pushdown(p);int mid = (l + r) >> 1, sum = 0;if(x <= mid) sum += query(lc, x, y);if(y > mid) sum += query(rc, x, y);return sum;
}int main()
{cin >> n >> m;build(1, 1, n);while(m--){int op, a, b; cin >> op >> a >> b;if(op == 0) modify(1, a, b);else cout << query(1, a, b) << endl;}return 0;
}
好的,今天的分享就到这里了~~再见!!!