SP3693题解报告
应该都是中国人吧,这个题目描述讲的比较通俗易懂。
首先,我们发现在 \([l, r]\) 中当 \((A_i + A_j)_{\max}\) 时,\(A_i\) 和 \(A_j\) 为 \([l, r]\) 中的最大值和非严格次大值。所以题目变成了单点赋值和查区间最大值和次大值。
我们发现,最大值和次大值这种东西是不具有可差分的性质的,因此不能使用树状数组,我们只能请出家里有矿的人在 WC 吃的东西线段树!,所以我们可以维护一个节点的最大值和次大值。由于此题是单点修改,所以不用懒惰标记,这也大大减少了实现难度!
思路不难,难点就是实现。
首先是 pushup,最大值很好处理,就是左右子树的最大值。对于次大值,它一定是左右子树中的最大值、次大值的一个。然而次大值的值可以说是跟在最大值屁股后面的,所以说次大值是左右子树中的最大值、次大值这 \(4\) 个值中最大的且小于等于最大值的数,就是这四个数的次大值。
如何求四个数的次大值呢?
最简单粗暴的方法是开一个数组,把它们四个存进去排序,输出第三个值。
所以求次大值的代码如下:
int sec(int a, int b, int c, int d){
int tmp[] = {a, b, c, d};
sort(tmp, tmp + 4);
return tmp[2];
}
那么 pushup 也出来了(mx 为最大值,mx2 为次大值):
void pushup(int u){
a[u].mx = max(a[ls(u)].mx, a[rs(u)].mx);
a[u].mx2 = sec(a[ls(u)].mx, a[rs(u)].mx, a[ls(u)].mx2, a[rs(u)].mx2);
}
接下来就是建树了。跟普通线段树没有什么区别,只需要注意,当 l == r 时,最大值是 a[l],没有次大值,所以设为 -inf,inf 是一个大数,代码就不给了。
然后就是单点修改。也没什么好说的,就是递归到 l == r 的时候直接更新最大值为要修改的值即可,代码不给了。
最后就是查询。我们可以用两个 ans1 和 ans2 表示最大值和次大值。由于线段树的递归是先左后右,所以当递归到需求的区间的子区间时,在它前面的子区间已经全部遍历过了。最大值的更新就不说了,同上面的分析,ans2 是前面区间的最大值,前面区间的次大值,当前子区间的最大值和次大值四个数中的次大值,用上面的 sec 函数即可。那么此题就做完了。
随后附上参考代码(删去了一些部分,直接复制会亖哦):
#include<bits/stdc++.h>
#define int long long
#define inf 1e17
using namespace std;
const int N = 1e5 + 5;
struct tree{
int mx, mx2;
}a[4 * N];
int n, q, b[N], x, y, ans1, ans2;
char op;
int ls(int u){return u << 1;}
int rs(int u){return u << 1 | 1;}
int sec(int a, int b, int c, int d){
int tmp[] = {a, b, c, d};
sort(tmp, tmp + 4);
return tmp[2];
}
void pushup(int u){
a[u].mx = max(a[ls(u)].mx, a[rs(u)].mx);
a[u].mx2 = sec(a[ls(u)].mx, a[rs(u)].mx, a[ls(u)].mx2, a[rs(u)].mx2);
}
void build(int u, int l, int r){
if(l == r){
a[u].mx = b[l];
a[u].mx2 = -inf;
return ;
}
// 省略
pushup(u);
}
void update(int u, int l, int r, int x, int y){
if(l == r){
a[u].mx = y;
return ;
}
//省略
pushup(u);
}
void query(int u, int l, int r, int L, int R){
if(L <= l && r <= R){
ans2 = sec(a[u].mx2, a[u].mx, ans1, ans2);
//省略ans1的更新
return ;
}
//省略基本操作
}
//省略 mian 函数