题目概述:
有N个数,进行M次操作
每个数有一个编号,编号从1递增,每个数为num
操作有两种,Q a b,查询区间a到b(含a,b)的最大的数,U a v,将第a个数改为v
输入:
第一行N,M,下一行有N个数,其后M行,每行一种操作,有多组数据,到EOF为止
限制:
0<N<=2e5;0<M<5000
输出:
每行一个整数,为每次查询的结果
样例输入:
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
样例输出:
5
6
5
9
讨论:
树状数组求最值的问题,其实只能算借用树状数组进行暴力求解而已,不过看上去速度着实优于最朴素的办法,这部分在理解上的困难远超树状数组求和,虽然写了不少注释,然而仍然无法改变这是个水题的现实
1.将b减1提高一层深度(第一次调用可能不会提高深度),并遍历其左兄弟(即比其下标小的深度最小的各个节点,即便是第一次调用,这包含了其子节点中的最大值,但不会比较a所在子树,因为那时b为a祖先,而b的左兄弟已然小于a,这时只会跳出循环)
2.虽然不比较a所在子树最大值(因为最大值可能由下标小于a的某个数产生),但会比较b最后一个左兄弟(即a在b左兄弟所在层的祖先)的下标的原始数据,因为下一次循环深度提升后便不会再考虑该点原始值
3.配合下一行p-o即是遍历p所在子树中,下标比p小且仅比p深一层的所有节点,这些节点包括了p所在子树中下标小于p的所有节点的最大值,配合该循环所在的上层循环,使得p节点总是能更新为下标比p小的节点的最大值(这里不必考虑下标比a小的节点的影响,因为更新前这些节点已然受到那些节点影响,这里更新主要是针对a的变化所带来的影响),而p的深度逐次降低,直到整棵树的根节点,至此a变化所带来的影响全部生效
题解状态:
1716MS,3280K,1205 B,C++
#include<algorithm>
using namespace std;
#define INF 0x6f6f6f6f
#define maxx(a,b) ((a)>(b)?(a):(b))
#define minn(a,b) ((a)<(b)?(a):(b))//这两个函数太常用而又懒得写,改成宏函数看上去还能简洁一些,并且也没有函数调用的过程了
#define MAXN 200005//取N的最大值+5
#define lb(a) (a&(-a))//lowbit,同样太常用而改成宏函数
int tree[MAXN];//树状数组的原始容器
int nums[MAXN];//原始数据容器,求和时可以不用,但求最值时由于子节点可能会覆盖父节点的值因而需要查原始数据才能正确比较
char cmd[2];//存放操作的字符串
inline int query(int a, int b)//查询区间a到b(含a,b)的最大值,额看的资料上的代码写的更加混乱,改成这种形式能清楚一些
{
int ans = nums[b];//取该区间最右端的值作初始值
while (a != b) {
for (b -= 1; b - lb(b) >= a; b -= lb(b))//参见讨论1
ans = maxx(ans, tree[b]);
ans = maxx(ans, nums[b]);//参见讨论2
}
return ans;
}
inline void modify(int a, int v, int N)
{
nums[a] = v;//顺便从原始数据上进行修改
for (int p = a; p <= N; p += lb(p)) {//遍历其祖先节点
tree[p] = v;//暂时将p改为v,这可能是一个较小的值(会在接下来更新为原最大值),或一个较大的值(作为新的最大值)
for (int o = 1; o < lb(p); o <<= 1)//参见讨论3
tree[p] = maxx(tree[p], tree[p - o]);//重新比较以选出新的最大值
}
}
inline void fun(int N, int M)
{
for (int p = 1; p <= N; p++) {
scanf("%d", &nums[p]);//input
modify(p, nums[p], N);
}
while (M--) {
int a, b;
scanf("%s", cmd);//input//直接录入字符串便无需考虑丢弃换行符的问题
switch (cmd[0]) {
case'Q':
scanf("%d%d", &a, &b);//input
printf("%d\n", query(a, b));//output
break;
case'U':
scanf("%d%d", &a, &b);//input
modify(a, b, N);
}
}
}
int main(void)
{
freopen("vs_cin.txt", "r", stdin);
freopen("vs_cout.txt", "w", stdout);
int N, M;
while (~scanf("%d%d", &N, &M)) {//input
memset(tree, -1, sizeof(tree));//这里填充-1或0都没什么影响,只是-1看上去更明显一些
fun(N, M);
}
}
EOF