P3372 【模板】线段树 1
线段树模板题
这个题的题面十分好理解,不再进行过多解释。
做题思路(数组)
建树与维护
建树代码
void build(int l, int r, int & rt) {//递归建树
rt = ++tot;
tr[rt].l = l, tr[rt].r = r;
if (l == r) {
scanf("%lld", &tr[rt].v);
return;
}
int mid = (l + r) >> 1;
build(l, mid, tr[rt].lc);
build(mid + 1, r, tr[rt].rc);
push_up(tr[rt].lc, tr[rt].rc, rt); //对信息进行维护
}
维护代码
void push_up(int lc, int rc, int rt) {
tr[rt].v = tr[lc].v + tr[rc].v;
}
区间修改实现
void push_down(int lc, int rc, int rt) {
tr[lc].tag += tr[rt].tag;
tr[rc].tag += tr[rt].tag;
tr[lc].v += (tr[lc].r - tr[lc].l + 1) * tr[rt].tag; //修改区间和
tr[rc].v += (tr[rc].r - tr[rc].l + 1) * tr[rt].tag;
tr[rt].tag = 0;
}
void up_date(int l, int r, int rt) {
if (x <= l && y >= r) {
tr[rt].tag += k;
tr[rt].v += (r - l + 1) * k;
return;
}
push_down(tr[rt].lc, tr[rt].rc, rt);
int mid = (l + r) >> 1;
if (x <= mid)
up_date(l, mid, tr[rt].lc);
if (y > mid)
up_date(mid + 1, r, tr[rt].rc);
push_up(tr[rt].lc, tr[rt].rc, rt);
}
long long get_ans(int l, int r, int rt) {
if (x <= l && y >= r)
return tr[rt].v;
push_down(tr[rt].lc, tr[rt].rc, rt);
ll ans = 0;
int mid = (l + r) >> 1;
if (x <= mid)
ans += get_ans(l, mid, tr[rt].lc);
if (y > mid)
ans += get_ans(mid + 1, r, tr[rt].rc);
return ans;
}
代码实现(数组)
#include <cstdio>
typedef long long int ll;
using namespace std;
#define N 200100
int n, m, root, tot, p, x, y;
ll k;
struct node {
int l, r, lc, rc;
long long v, tag;
} tr[N];
void push_up(int lc, int rc, int rt) {
tr[rt].v = tr[lc].v + tr[rc].v;
}
void push_down(int lc, int rc, int rt) {
tr[lc].tag += tr[rt].tag;
tr[rc].tag += tr[rt].tag;
tr[lc].v += (tr[lc].r - tr[lc].l + 1) * tr[rt].tag; //修改区间和
tr[rc].v += (tr[rc].r - tr[rc].l + 1) * tr[rt].tag;
tr[rt].tag = 0;
}
void build(int l, int r, int & rt) {
rt = ++tot;
tr[rt].l = l, tr[rt].r = r;
if (l == r) {
scanf("%lld", &tr[rt].v);
return;
}
int mid = (l + r) >> 1;
build(l, mid, tr[rt].lc);
build(mid + 1, r, tr[rt].rc);
push_up(tr[rt].lc, tr[rt].rc, rt);
}
void up_date(int l, int r, int rt) {
if (x <= l && y >= r) {
tr[rt].tag += k;
tr[rt].v += (r - l + 1) * k;
return;
}
push_down(tr[rt].lc, tr[rt].rc, rt);
int mid = (l + r) >> 1;
if (x <= mid)
up_date(l, mid, tr[rt].lc);
if (y > mid)
up_date(mid + 1, r, tr[rt].rc);
push_up(tr[rt].lc, tr[rt].rc, rt);
}
ll get_ans(int l, int r, int rt) {
if (x <= l && y >= r)
return tr[rt].v;
push_down(tr[rt].lc, tr[rt].rc, rt);
ll ans = 0;
int mid = (l + r) >> 1;
if (x <= mid)
ans += get_ans(l, mid, tr[rt].lc);
if (y > mid)
ans += get_ans(mid + 1, r, tr[rt].rc);
return ans;
}
int main() {
scanf("%d %d", &n, &m);
build(1, n, root);
while (m--) {
scanf("%d %d %d", &p, &x, &y);
if (p == 1) {
scanf("%lld", &k);
up_date(1, n, 1);
} else
printf("%lld\n", get_ans(1, n, 1));
}
return 0;
}
下面是指针实现
虽然使用指针来写线段树的人很少,但是个人觉得使用指针写线段树将更加容易理解。
做题思路(指针)
定义结构体
struct Node {
ll tag, v;//延迟标记,子节点值
int l, r;//左端点,右端点
Node *ls, *rs;//左孩子、右孩子
}
建树
Node(const int L, const int R) {//构造函数
l = L; r = R; //当前结点左右端点
if (l == r) {//叶子结点
tag = 0;//清空延迟标记
v = a[l];//其值等于这个结点对应的值本身
ls = rs = NULL;//没有左右孩子结点
} else {
tag = 0;
int M = (L + R) >> 1;//取中点
ls = new Node(L, M);//递归向下一层进行构造
rs = new Node(M + 1, R);
pushup();//值等于左右孩子结点值的和
}
}
维护信息
inline void pushup() {//上传信息
v = ls->v + rs->v;//当前结点值=左结点值+右结点值
}
区间修改的实现
inline void maketag(const ll w) {//给一个结点打延迟标记
v += (r - l + 1) * w;//更新当前结点值
tag += w;//延迟标记
}
inline void pushdown() {//下传延迟标记
if (tag == 0) return;//当前结点没有延迟标记,直接返回
ls->maketag(tag);//向左右孩子打标记
rs->maketag(tag);
tag = 0;//清空当前结点延迟标记
}
inline bool InRange(const int L, const int R) { return (L <= l) && (r <= R); }
//一个结点被完全包含
inline bool OutofRange(const int L, const int R) { return (l > R) || (r < L); }
//一个结点完全没有重叠(毫不相关)
void upd(const int L, const int R, const ll w) {//赋值操作
if (InRange(L, R)) {//完全包含
maketag(w);//先打上延迟标记
} else if (!OutofRange(L, R)) {//有重叠部分但没有完全包含
pushdown();//下传延迟标记到子节点
ls->upd(L, R, w);//递归更改信息
rs->upd(L, R, w);
pushup();//向上传递信息
}
}
ll qry(const int L, const int R) {//询问一段区间的和
if (InRange(L, R)) return v;//完全包含直接返回这个结点的值
if (OutofRange(L, R)) return 0;//毫不相关,返回0
pushdown();//下传
return ls->qry(L, R) + rs->qry(L, R);//递归询问
}
代码实现(指针)
#include <cstdio>
const int maxn = 100005;
typedef long long int ll;
int n, q;
ll a[maxn];//原数组
struct Node {
ll tag, v;//延迟标记,子节点值
int l, r;//左端点,右端点
Node *ls, *rs;//左孩子、右孩子
inline void maketag(const ll w) {//给一个结点打延迟标记
v += (r - l + 1) * w;//更新当前结点值
tag += w;//延迟标记
}
inline void pushup() {//上传信息
v = ls->v + rs->v;//当前结点值=左结点值+右结点值
}
inline void pushdown() {//下传延迟标记
if (tag == 0) return;//当前结点没有延迟标记,直接返回
ls->maketag(tag);//向左右孩子打标记
rs->maketag(tag);
tag = 0;//清空当前结点延迟标记
}
Node(const int L, const int R) {//构造函数
l = L; r = R; //当前结点左右端点
if (l == r) {//叶子结点
tag = 0;//清空延迟标记
v = a[l];//其值等于这个结点对应的值本身
ls = rs = NULL;//没有左右孩子结点
} else {
tag = 0;
int M = (L + R) >> 1;//取中点
ls = new Node(L, M);//递归向下一层进行构造
rs = new Node(M + 1, R);
pushup();//值等于左右孩子结点值的和
}
}
// this l, r
inline bool InRange(const int L, const int R) { return (L <= l) && (r <= R); }
//一个结点被完全包含
inline bool OutofRange(const int L, const int R) { return (l > R) || (r < L); }
//一个结点完全没有重叠(毫不相关)
void upd(const int L, const int R, const ll w) {//赋值操作
if (InRange(L, R)) {//完全包含
maketag(w);//先打上延迟标记
} else if (!OutofRange(L, R)) {//有重叠部分但没有完全包含
pushdown();//下传延迟标记到子节点
ls->upd(L, R, w);//递归更改信息
rs->upd(L, R, w);
pushup();//向上传递信息
}
}
ll qry(const int L, const int R) {//询问一段区间的和
if (InRange(L, R)) return v;//完全包含直接返回这个结点的值
if (OutofRange(L, R)) return 0;//毫不相关,返回0
pushdown();//下传
return ls->qry(L, R) + rs->qry(L, R);//递归询问
}
};
int main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i) scanf("%lld", a + i);
Node *rot = new Node(1, n);
for (ll o, x, y, z; q; --q) {
scanf("%lld%lld%lld", &o, &x, &y);
if (o == 1) {
scanf("%lld", &z);
rot->upd(x, y, z);
} else {
printf("%lld\n", rot->qry(x, y));
}
}
return 0;
}
鸣谢
感谢zay大佬讲解线段树的指针实现,zay Orz !