上一期讲了线段树的知识,搞个例题玩一下
题目背景
很多学校流行一种比较的习惯。老师们很喜欢询问,从某某到某某当中,分数最高的是多少。这让很多学生很反感。
题目描述
不管你喜不喜欢,现在需要你做的是,就是按照老师的要求,写一个程序,模拟老师的询问。当然,老师有时候需要更新某位同学的成绩。
## 输入格式
第一行,有两个正整数 n 和 m,分别代表学生的数目和操作的数目。学生 ID 编号分别从 1 编到 n。
第二行包含 n 个整数,代表这 n 个学生的初始成绩,其中第 i 个数代表 ID 为 i 的学生的成绩,保证学生的成绩为 1 - 10^9 之间的正整数。
接下来有 m 行。每一行有一个字符 c(只取 `Q` 或 `U`),和两个正整数a,b。
- 当 c 为 `Q` 的时候,表示这是一条询问操作,它询问 ID 从 a 到 b(包括 a,b) 的学生当中,成绩最高的是多少;
- 当 c 为 `U` 的时候,表示这是一条更新操作,如果当前 a 学生的成绩低于 b,则把 ID 为 a 的学生的成绩更改为 b,否则不改动。
## 输出格式
对于每一次询问操作输出一行一个整数,表示最高成绩。
## 输入输出样例 #1
### 输入 #1
5 6
1 2 3 4 5
Q 1 5
U 3 6
Q 3 4
Q 4 5
U 2 9
Q 1 5
### 输出 #1
5
6
5
9
题目大意
给定 n 个学生的初始成绩,接下来执行 m 次操作,操作分为两种:
- 查询 Q a b:查询编号 a∼b 区间内的最高分数;
- 更新 U a b:若编号为 a 的学生当前成绩小于 b,则将其成绩修改为 b,否则不改
算法选型分析
暴力解法(不可行)
ZKW 线段树基础知识
1. 概念由来
ZKW 线段树得名于其提出者张昆玮(缩写 zkw),它是非递归形式的线段树 ,核心思想为自底向上遍历操作,也是算法竞赛里高频使用的模板。
2. 核心结构
ZKW 线段树会把原数组补全为一棵满二叉树:
- 设定变量
bit:代表大于等于元素总数 n 的最小 2 的整数次幂,也是叶子节点区域的起始分界; - 叶子节点:专门用来存储原始的学生成绩,对应原数组的每一个元素;
- 上层节点:每个节点存储对应区间的合并结果,本题中即区间最大值;
- 节点关系:对于任意节点,左子节点、右子节点可以通过位运算快速定位,父子节点关联规则固定。
3. 适用场景与优缺点
- 适用场景:最擅长处理 单点修改 + 区间求和、区间最值 类问题;
- 优点:无递归调用,运行常数小、速度快,代码模板简洁;
- 缺点:实现带懒标记的区间加减、区间乘等复杂操作难度较高,一般不用于这类题型。
解题思路
结合题目要求与 ZKW 线段树的特性,整体分为四大步骤:
-
建树 先计算出分界值
bit,将原始成绩存入线段树的叶子节点;再从底层向上遍历,逐层合并区间最大值,完成整棵线段树的初始化。 -
单点更新收到更新指令后,先定位到对应学生所在的叶子节点;按照题目规则判断是否需要修改分数,若需要修改,则从该叶子节点一路向上更新所有父节点的区间最大值,保证整棵树数据始终正确。
-
区间查询接到查询指令后,将查询区间的左右端点映射到叶子节点下标;通过双指针从底层向根节点移动,沿途收集所有合法区间的最大值,最终得到整个查询区间的最高分。
-
批量处理操作循环处理全部 m 条指令,根据指令类型,分别调用更新、查询逻辑,并按要求输出查询结果。
code
cpp
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1 << 19;
int n,m,tree[MAXN],bit;
inline void build(int n){
for(bit=1;bit<=n;bit<<=1);
for(int i=bit+1;i<=bit+n;i++){
cin>>tree[i];
}
for(int i=bit-1;i;i--){
tree[i]=max(tree[i<<1],tree[i<<1|1]);
}
}
inline void update(int ind,int val){
int pos=bit+ind;
tree[pos]=val;
for(pos>>=1;pos;pos>>=1){
tree[pos]=max(tree[pos<<1],tree[pos<<1|1]);
}
}
inline int query(int l,int r){
int res=INT_MIN;
l+=bit-1;
r+=bit+1;
while(l^r^1){
if(!(l&1)) res=max(res,tree[l^1]);
if(r&1) res=max(res,tree[r^1]);
l>>=1;
r>>=1;
}
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin>>n>>m;
build(n);
while(m--){
char c;
int a,b;
cin>>c>>a>>b;
if(c=='Q'){
cout<<query(a,b)<<'\n';
}else if(c=='U'){
if(tree[bit+a]<b){
update(a,b);
}
}
}
return 0;
}