线段树-小白逛公园(nkoj1316)
这个题稍微难一点
题意分析:维护和查询最大区间子序和,区间大小N,维护和查询次数共M
先了解一下最大子序和:给一个序列,找最大的区间和
LeetCode53. 最大子序和
了解一下O(n)的做法
- 枚举序列元素,一个变量
sum
记录当前区间的和,一个ans
记录最大的和,如果sum
比ans
大就更新ans
,如果sum
小于0就抛弃sum
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum = 0, res = nums.at(0);
for (int i = 0; i < nums.size(); i++) {
sum += nums.at(i);
// 更新现有的最大的
res = max(res, sum);
// 如果前面和小于零了那就可以抛弃了
sum = max(sum, 0);
}
return res;
}
};
- PS:如果需要知道具体区间则需要
suml
和sumr
记录左右端点的变化
本题的差异
复杂度分析:N是 ,M是 ,最坏情况 ,O(n)做法是不行的,需要找一个log(n)的结构:线段树
线段树复习预习
首先考虑节点存的东西:由于需要查询最大区间和,所以需要维护
- max:最大子区间和
- maxl:从左端点开始的最大子区间和
- maxr:从右端点开始的最大子区间和
- sum:整个区间的和
然后是线段树的操作
- build:建立整棵树,递归实现,到达递归基的时候把用读入值设置节点信息,返回的时候需要用左右儿子更新本节点信息(update)
- modify:将第p个位置的值改为s,同样递归实现,找到p的时候用s设置节点信息,返回的时候更新
- query:查询a,b区间内的最大子区间和,比较复杂,分为四种情况(四种情况的最大值作为解):
- 递归基:l,r在a,b里面,直接返回本节点的max
- 左儿子的子区间有候选解(a<=mid):递归查询左子树
- 右儿子的子区间有候选解(mid+1<=b):递归查询右子树
- 候选解和左右儿子都有交(a<=mid&&mid+1<=b):从mid向左的最大子区间和+从mid+1向右的最大子区间和作为候选解
本题坑点:输入数据[a,b]竟然有a>b的情况出现,需要处理一下
代码
struct SegNode {
int maxl, max, maxr, sum;
void set(int tmp) {
maxl = tmp;
max = tmp;
maxr = tmp;
sum = tmp;
}
};
struct SegTree {
int n;
SegNode* nodes;
SegTree(int n) {
this->n = n;
nodes = new SegNode[1 << 20];
build(1, 1, n);
}
void update(int i, int l, int r) {
int lson = i << 1;
int rson = i << 1 | 1;
nodes[i].sum = nodes[lson].sum + nodes[rson].sum;
nodes[i].max = std::max(nodes[lson].max, nodes[rson].max);
nodes[i].max = std::max(nodes[i].max, nodes[lson].maxr + nodes[rson].maxl);
nodes[i].maxl = std::max(nodes[lson].maxl, nodes[lson].sum + nodes[rson].maxl);
nodes[i].maxr = std::max(nodes[rson].maxr, nodes[rson].sum + nodes[lson].maxr);
}
void build(int i, int l, int r) {
// 到底了
if (l == r) {
int tmp;
// scanf("%d", &tmp);
read_int(tmp);
// printf("tmp=%d\n", tmp);
nodes[i].set(tmp);
return;
}
int mid = (l + r) >> 1;
build(i << 1, l, mid);
build(i << 1 | 1, mid + 1, r);
update(i, l, r);
}
void modify(int p, int s) {
modify(1, 1, n, p, s);
}
void modify(int i, int l, int r, int p, int s) {
// l == r == p
if (l == r) {
nodes[i].set(s);
return;
}
int mid = (l + r) >> 1;
if (p <= mid) {
modify(i << 1, l, mid, p, s);
} else {
modify(i << 1 | 1, mid + 1, r, p, s);
}
update(i, l, r);
}
int query(int a, int b) {
if (a > b) {
int tmp = b;
b = a;
a = tmp;
}
return query(1, 1, n, a, b);
}
int queryl(int i, int l, int r, int b) {
// l,r在l,b里面
if (r <= b) {
return nodes[i].maxl;
}
int mid = (l + r) >> 1;
int ans = -INF;
ans = queryl(i << 1, l, mid, b);
if (b >= mid + 1) {
ans = std::max(ans, nodes[i << 1].sum + queryl(i << 1 | 1, mid + 1, r, b));
}
return ans;
}
int queryr(int i, int l, int r, int a) {
// l,r在a,r里面
if (a <= l) {
return nodes[i].maxr;
}
int mid = (l + r) >> 1;
int ans = -INF;
ans = queryr(i << 1 | 1, mid + 1, r, a);
if (a <= mid) {
ans = std::max(ans, nodes[i << 1 | 1].sum + queryr(i << 1, l, mid, a));
}
return ans;
}
int query(int i, int l, int r, int a, int b) {
// l,r在a,b里面
if (a <= l && r <= b) {
return nodes[i].max;
}
int mid = (l + r) >> 1;
int ans = -INF;
// 左儿子有候选解
if (a <= mid) {
ans = query(i << 1, l, mid, a, b);
}
// 右儿子有候选解
if (b >= mid + 1) {
ans = std::max(ans, query(i << 1 | 1, mid + 1, r, a, b));
}
// 候选解和左右儿子都有交
if (a <= mid && b >= mid + 1) {
ans = std::max(ans, queryr(i << 1, l, mid, a) + queryl(i << 1 | 1, mid + 1, r, b));
}
return ans;
}
};