「2021 集训队互测」学姐买瓜
考虑暴力
容易写出一下代码
void solve(int L, int R) {
len = 0;
for (int i = 1 ; i <= tot ; i++)
if (L <= t[i].L && t[i].R <= R)len++, q[len] = t[i];
int ans = 0, last = 0;
sort(q + 1, q + 1 + len, cmp);
for (int i = 1 ; i <= len ; i++)if (last <= q[i].L)last = q[i].R + 1, ans++;
cout << ans << endl;
}
提炼一下上面暴力的主要思想:
从左往右,对于一个线段能放就放(满足 已经放置的 最右 线段端点 <= 当前要放置的 线段 的左端点)
考虑快速处理上述 过程--------------对于每一个 在原序列里面的线段\(S\) 维护一个后缀 区间 \(P\)
满足 \(R_S < L_p , 且R_p最小\)
暴力的想法: 每插入一个线段 就对 所有的 线段 看看 这个后继是否更新
直接做直接寄
考虑优化这个过程
需要支持 :
区间修改后继
快速跳后继
于是就可以想到分块
考虑对原序列分成\(B\)块
块内: 维护块内 每一个块 往右跳线段 跳出这个块 所到达的 最靠左 的块,即相应的点
每一个位置 跳一次线段后 不跳出块内 所能 到达的 最靠左的位置
块外: 维护一个整体 修改这个后继的标记 (即整体取min)
于是就可以写写代码了(借鉴了某位老哥的代码吧)
fa[i] 在i这个块内跳一次所到达的 位置
to[i] 以i这个位置为起点,跳出这个块 所能到达的最左位置
tag[i] 区间取min
v[i]: 相应的跳跃次数
inline void ins(int L , int R){
for(int i = 1 ; i < rk[L] ; i++)tag[i] = min(tag[i] , R + 1);
if(tag[rk[L]] != INF){
for(int i = RR[rk[L]] ; i >= LL[rk[L]] ; i--){
if(tag[rk[L]] < fa[i]){
fa[i] = to[i] = tag[rk[L]] , v[i] = 1;
}
}
tag[rk[L]] = INF;
}
if(rk[L] == rk[R + 1]){
for(int i = L ; i >= LL[rk[L]] ; i--){
if(R + 1 < fa[i]){
fa[i] = R + 1;
v[i] = v[R + 1] + 1;
}
}
}
else{
for(int i = L ; i >= LL[rk[L]] ; i--){
if(R + 1 < fa[i]){
fa[i] = to[i] = R + 1;
v[i] = 1;
}
}
}
for(int i = RR[rk[L]] ; i >= LL[rk[L]] ; i--){
if(fa[i] <= RR[rk[L]]){
v[i] = v[fa[i]] + 1;
to[i] = to[fa[i]];
}
}
}
inline void solve(int L , int R){
int now = L , zz = 0;
int minl = INF , minl2 = INF , V;
while(now <= R){
minl = min(tag[rk[now]] , fa[now]);
minl2 = min(tag[rk[now]] , to[now]);
V = v[now];
if(minl > R + 1)break;
if(minl2 <= R + 1)zz += V , now = minl2;
else zz++ , now = minl;
}
printf("%d\n" , zz);
}