AcWing 245. 你能回答这些问题吗
题目传送门
主要说一下一个新手普遍疑惑的地方。
为什么\(query\)返回值是结构体?
假设你查询区间\([l, r]\)
并且现在你递归到当前节点\(p\),其左儿子为\(p1\),右儿子为\(p2\)。
并且现在区间\([l, r]\)同时跨到了\(p1\)和\(p2\)。
按照以前区间查询单点修改的线段树,我们会这样写:
return max(query(p1,l,mid), query(p2,mid+1,r))
这样写就漏解了,注意,上面这句话的实际含义是只比较了\(p1\)的最大子段和 和 \(p2\)的最大子段和,漏掉了跨过\(mid\)的最大子段和。
所以你应该这么写:
return max(query(p1,l,mid), query(p2,mid+1,r), 跨过mid的最大子段和)
我们可以发现,“跨过\(mid\)的最大子段和”无法表达出来,怎么办呢?
其实很简单,你把query(p1,l,mid)看成一个左儿子,query(p2,mid+1,r)看成右儿子,然后新开一个父亲。对这三个节点做一次\(pushup\)就行了。然后返回父亲的\(tmax\)域即可。
因为\(pushup\)里头父亲、左儿子、右儿子的每个域都要用到,所以将\(query\)函数的返回类型换成结构体就行了。
#include
using namespace std;
const int N = 500010;
int n, m;
int a[N]; //临时数组,用于装一下输入的数字
struct Node {
int l, r;
int sum; // 区间和
int lmax; //左后缀最大和
int rmax; //右前缀最大和
int tmax; //整体最大和
} tr[N << 2];
//向祖先推送信息
//注意这里的Node是用的地址符,即按地址传递参数,函数内修改的是原来的变量
//因为pushup需要在很多地方用,所以这里多写一层
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); //三者取max
}
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) {
int x = a[r];
// lmax:因为只有一个,所以是a[r]
// rmax:因为只有一个,所以是a[r]
// tmax:因为只有一个,所以是a[r]
// sum:区间总和是a[r]
tr[u] = {l, r, x, x, x, x};
return;
}
tr[u] = {l, r}; //构建最重要的就是设置好范围
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
//由子节点信息算一下父节点信息
pushup(u);
}
//在以u节点为根的子树中,将位置x的值修改为v
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}; // l=r=x:因为只有老哥一个 sum=v lmax=v,rmax=v,tmax=v
//注意:叶子节点的更改,是不需要pushup(u)的!!可以理解为此时u没有子节点了
else {
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) //那么x一定在左半边
modify(u << 1, x, v);
else // x一定在右半边
modify(u << 1 | 1, x, v);
//由于更新了左半边或者右半边中的某一个数据,所以需要再次由子节点信息算一下父节点信息
pushup(u);
}
}
//查询的时候也需要算那四个数的,因为查的时候也可能涉及到区间合并的,WHY?
Node query(int u, int l, int r) {
//要查找的区间tr[u].l ~ tr[u].r 包含了[l,r]这个区间
if (tr[u].l >= l && tr[u].r <= r) return tr[u];
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; //这里的res其实并不是一个真正在线段树中存在的节点,而是一个临时变量,方便利用pushup
//函数的计算逻辑,计算出tmax,这么写确实是可以复用代码,就是理解起来麻烦了
pushup(res, left, right);
return res;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
//构建树,root=1,范围[1,n]
build(1, 1, n);
int k, x, y;
while (m--) {
cin >> 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;
}