给定一个长度为 n 且初始值全为 0 的序列。你需要支持以下两种操作:
- Add L,R,h:将序列 [L,R] 内所有值小于 h 的元素都赋为 h ,此时不改变高度大于 h 的元素值
- Remove L,R,h :将序列 [L,R] 内所有值大于 h 的元素都赋为 h ,此时不改变高度小于 h 的元素值
你需要输出进行 k 次上述操作之后的序列。
这道题处看时没有一点想法, 脑海中只有“啊, 这怎么做?!”, 就先跳过去了.
后来做完 花神游历各国后 在一个晚上突然就有了想法: 维护区间的最大值, 最小值, 用来判断这个区间里的值是不是全都大于 h, 或小于 h. 感觉思路挺靠谱的, 就写了, 结果RE了...百思不得其解
先确认ql, qh的大小关系是否正确. 还是RE后, 我仔细想了想, 之前初学线段树时RE的原因, 是因为我无限递归了, 节点到了叶子节点时还是继续递归, 这样RE了很多次.
又观察 add 和 remove 函数里的 return 条件, 发现, 若是这个节点是叶子节点, 只要这个最大值最小值不满足条件还是会继续递归, 这就导致了RE.
我就在spread()函数前加了叶子节点手动return, 这样交之后, 出现了TLE, 这个时候我又继续想, 我是不是可以把不能修改的区间提前返回了呢? 发现确实可以这样, 在提交就AC了, 发现叶子节点判断有些多余, 删除后也AC了.
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
const int MAXN = 3e6 + 600;
using namespace std;
int n, k;
struct Segment{
int max, min, assTag;
} s[MAXN << 2 | 1];
int cmd, ql, qh, h;
inline void spread(int o){
if(s[o].assTag != -1){// 因为 h >= 0, 所以这里选择 -1 做初始标记
s[o << 1].max = s[o << 1 | 1].max = s[o].assTag;
s[o << 1].min = s[o << 1 | 1].min = s[o].assTag;
s[o << 1].assTag = s[o << 1 | 1].assTag = s[o].assTag;
s[o].assTag = -1;
}
}
inline void update(int o){
s[o].max = max(s[o << 1].max, s[o << 1 | 1].max);
s[o].min = min(s[o << 1].min, s[o << 1 | 1].min);
}
void build(int o, int lo, int hi){
s[o].assTag = -1;// 这里注意把所有节点的assTag都初始为 -1, 这个无法在update函数中更新
if(hi - lo < 2){
s[o].max = s[o].min = 0;
return;
}
int mi = (lo + hi) >> 1;
build(o << 1, lo, mi);
build(o << 1 | 1, mi, hi);
update(o);
}
void add(int o, int lo, int hi){
if(qh <= lo || hi <= ql){
return;
}
if(ql <= lo && hi <= qh){
if(s[o].max <= h){// 如果这个区间里的最大值小于等于h, 那么这个区间可以直接赋值
s[o].max = s[o].min = h;
s[o].assTag = h;
return;
}
if(h <= s[o].min){// 如果这个区间的最小值都大于等于h, 那么这个区间不用改变
return;
}
}
// if(hi - lo < 2){// 手动return, 其实多余
// return;
// }
spread(o);
int mi = (lo + hi) >> 1;
add(o << 1, lo, mi);
add(o << 1 | 1, mi, hi);
update(o);
}
void remove(int o, int lo, int hi){
if(qh <= lo || hi <= ql){
return;
}
if(ql <= lo && hi <= qh){// 同理
if(h <= s[o].min){
s[o].max = s[o].min = h;
s[o].assTag = h;
return;
}
if(s[o].max <= h){
return;
}
}
spread(o);
int mi = (lo + hi) >> 1;
remove(o << 1, lo, mi);
remove(o << 1 | 1, mi, hi);
update(o);
}
void print(int o, int lo, int hi){
if(hi - lo < 2){
printf("%d\n", s[o].min);
return;
}
spread(o);
int mi = (lo + hi) >> 1;
print(o << 1, lo, mi);
print(o << 1 | 1, mi, hi);
}
int main(){
scanf("%d%d", &n, &k);
build(1, 0, n);
for(int i = 0; i < k; ++i){
scanf("%d%d%d%d", &cmd, &ql, &qh, &h);
if(qh < ql){
swap(ql, qh);
}
++qh;
if(cmd == 1){
add(1, 0, n);
}
else{
remove(1, 0, n);
}
}
print(1, 0, n);
return 0;
}
讲道理这道题的编码复杂度很低, 更重要的思维.