暑假集训__STL 进阶
STL 进阶
一、 set,multiset
建议提前熟悉一下指针的用法,不用过多,随便看看就行
大家应该已经学过平衡树了, $ set $ 是一个用平衡树实现的 集合 ,可以实现平衡树基本的 $ ins , del , find ,lower_bound $ 之类,下面是用法表和复杂度(deepseek)
其实 $ multiset $ 和 $ set $ 的区别就在于 , set中每个元素最多只能存在一个,而multiset则可以存在多次。
set 的常用用法和时间复杂度
set 底层是平衡树(通常是红黑树),元素会自动有序,且不允许重复。
set<int> s;
s.insert(5);
s.insert(3);
s.insert(5); // 重复插入不会生效
常用操作:
set<int> s = {1, 3, 5};
s.insert(4); // 插入元素
s.erase(3); // 删除指定值
s.erase(s.begin()); // 删除迭代器指向的元素
s.find(4); // 查找,找不到返回 s.end()
s.lower_bound(4); // 找第一个 >= 4 的位置
s.upper_bound(4); // 找第一个 > 4 的位置
s.begin(); s.end(); // 头尾迭代器
s.size(); s.empty(); // 大小和是否为空
s.clear(); // 清空
时间复杂度:
| 操作 | 说明 | 复杂度 |
|---|---|---|
| insert(x) | 插入元素 | \(O(\log n)\) |
| erase(x) | 删除指定值 | \(O(\log n)\) |
| erase(it) | 删除迭代器指向元素 | \(O(\log n)\) |
| find(x) | 查找元素 | \(O(\log n)\) |
| lower_bound(x) | 找第一个 \(\ge x\) 的位置 | \(O(\log n)\) |
| upper_bound(x) | 找第一个 \(> x\) 的位置 | \(O(\log n)\) |
| count(x) | 统计元素个数(set 里只有 0/1) | \(O(\log n)\) |
| begin / end | 获取头尾迭代器 | \(O(1)\) |
| size / empty | 获取大小/判断是否为空 | \(O(1)\) |
| clear | 清空整个集合 | \(O(n)\) |
| swap | 交换两个集合 | \(O(1)\) |
| 遍历整个集合 | 例如 for(auto x : s) | \(O(n)\) |
注意:set 通常不能直接修改已有元素的值;如果要改,通常先删再插。
set<int> s;
s.insert(1);s.insert(3);
s.insert(3);//这次是不会再插入一个3,所以最终 s 就是 {1,3} . 并且insert是有返回值的,返回一个pair数组,first -> 一个指针,指向新插入的位置 ,second -> 一个bool,表示是否成功插入(1 -> yes)
multiset<int> s;
s.insert(1);s.insert(3);
s.insert(3);//最终s为 {1,3,3},同时insert返回的值只有一个指针指向新插入的位置。
给以先做一下 【深基17.例5】木材仓库 熟悉一下set的用法
简单熟悉set的用法后,可以开始学习珂朵莉了。
珂朵莉
珂朵莉其实是用来解决一种可以实现区间推平的操作的题,而且数据得随机,以实现区间分类尽量少。
原理就是如果一段区间内的值都相同,那么我没必要分开来维护每一个点的值,可以挂一个 $ {l,r,v} $ 来统一维护他们。通过暴力枚举来操作,通过区间推平操作来平衡复杂度。
下面接着珂朵莉源题讲具体实现。
例题 : CF896C Willem, Chtholly and Seniorious
题目描述
——威廉……
——怎么了?
——好像瑟尼欧里斯出问题了……
——我去看看……

瑟尼欧里斯是通过特定顺序连接特殊护符制成的。
经过了 500 多年,这个圣剑现在状况不太好,所以威廉决定彻底检查它。
瑟尼欧里斯有 \(n\) 片护符。威廉将它们排成一行,第 \(i\) 片护符是一个整数 \(a _ i\)。
为了维护它,威廉需要执行 \(m\) 次操作。
有四种操作类型:
- \(1\ l\ r\ x\) :对于 \(i\) 满足 \(l \le i \le r\),将 \(a _ i + x\) 赋值给 \(a _ i\)。
- \(2\ l\ r\ x\) :对于 \(i\) 满足 \(l \le i \le r\),将 \(x\) 赋值给 \(a _ i\)。
- \(3\ l\ r\ x\) :输出范围 \([l, r]\) 中第 \(x\) 小的数字,即在所有满足 \(l \le i \le r\) 的 \(a _ i\) 排序后第 \(x\) 小的数字。保证 \(1 \le x \le r - l + 1\)。
- \(4\ l\ r\ x\ y\) :输出范围 \([l, r]\) 中所有 \(a _ i\) 的 \(x\) 次幂之和模 \(y\),即 \(\left( \sum _ {i = l} ^ r {a _ i} ^ x \right) \bmod y\)。
输入格式
第一行包含四个整数 \(n, m, seed, v _ {max}\)(\(1 \le n, m \le 10 ^ 5\),\(0 \le seed < 10 ^ 9 + 7\),\(1 \le v _ {max} \le 10 ^ 9\))。
初始值和操作通过如下伪代码生成:
def rnd():
ret = seed
seed = (seed * 7 + 13) mod 1000000007
return ret
for i = 1 to n:
a[i] = (rnd() mod vmax) + 1
for i = 1 to m:
op = (rnd() mod 4) + 1
l = (rnd() mod n) + 1
r = (rnd() mod n) + 1
if (l > r):
swap(l, r)
if (op == 3):
x = (rnd() mod (r - l + 1)) + 1
else:
x = (rnd() mod vmax) + 1
if (op == 4):
y = (rnd() mod vmax) + 1
这里的 \(op\) 是题目中提到的操作类型。
输出格式
对于每个类型为 \(3\) 或 \(4\) 的操作,输出答案。
其实这题主要是因为有了操作二区间推平和数据的随即构造(是的,我甚至没发现这是随即构造),才得以让使用珂朵莉不同于暴力。
首先我们应构造一个结构体来存储各区间
struct jj{
mutable int l,r,v;//我们说了set一般不能改,但mutable 表示可以改变,用以改变被const限制的变量,主要是减少常数(提升较大),毕竟珂朵莉本身就是卡常
inline bool operator <(const jj a)const {return l<a.l;}
};
set<jj> s;
接下来我们考虑在维护过程中的 $ 1,3,4 $ 操作都是对一段区间进行操作或询问,首先我们需要把 $ [l,r] $ 这段区间都找出来,这时就需要一个珂朵莉中的核心操作:
split
用以找到以 $ x $ 为起始的一段区间的指针,如果 $ x $ 在珂朵莉中一段 $ [l1,r1] $ , 的区间内,就把其分成 $ [l1,x-1] , [x,r1] $ ,并返回后者的指针。
inline auto fen(int x){
auto it=s.lower_bound({x,0,0});// it 是指针
if(it!=s.end()&&it->l==x)return it;//刚好找到了一段以 x 为起始的区间,直接返回 it
--it;//没有刚好以x为起始的区间,it指向的是更大的,--后指向的就是 x 所在的一段区间
if(it->r<x)return s.end();//此类情况出现于 x > 整段区间长度len,所以根本找不到,但要特判,不然会传回错误的指针
int r=it->r,v=it->v;//暂存,下面要修改it
it->r=x-1;//mutable 直接修改卡常,分出 [l,x-1]
return s.insert({x,r,v}).fi;//插入[x,r],值为v,返回的是指针
}
推平
将一整段区间 $ [l,r] $ 全部修改为值为 $ v $ ,直接看代码
inline void tui(int l,int r,int v){
auto i2=fen(r+1),i1=fen(l);i1->r=r,i1->v=v;
s.erase(++i1,i2);
}
值得注意的是,我们在找 $ i1,i2 $ 时,必须先找到后面的 $ i2 $ 再找前面的 $ i1 $ ,我们考虑这样一种情况
假设有一段区间 $ [1,10] $ ,要推平的区间为 $ [3,5] $ ,如果先找到 $ i1 $ 就会有 $ [1,2],[3,10] $ ,再次找 $ i2 $ 时,会涉及到对 $ i1 $ 所存在指针的修改,这样 $ i1 $ 可能会被扰动,可能RE或WA。(当然基本上只要不出现erase这种操作就不会爆炸,按照我这种写法,前后不重要,但是如果你自己换了一种写法很可能会爆炸,而且后面有不少题需要很多不同操作,建议按照这种顺序写)
其他的操作很容易实现,就不写了。
码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
typedef unsigned long long ull;
typedef pair<int,int> pii;
#define fi first
#define se second
#define ps push_back
#define mk make_pair
const int N=2e5+10,inf=0x3f3f3f3f,mod=1e9+7;
inline ll read(){
char c=getchar();ll x=0;bool f=0;
while(!isdigit(c))f=c=='-'?1:0,c=getchar();
while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
return f?-x:x;
}
struct jj{
mutable int l,r,v;
inline bool operator <(const jj a)const {return l<a.l;}
};
set<jj> s;
inline auto fen(int x){
auto it=s.lower_bound({x,0,0});
if(it!=s.end()&&it->l==x)return it;
--it;
if(it->r<x)return s.end();
int r=it->r,v=it->v;
it->r=x-1;return s.insert({x,r,v}).fi;
}
int n,m,sd,vmax;
inline int rd(){
int ans=sd;
sd=(sd*7+13)%mod;
return ans;
}
inline void add(int l,int r,int v){
auto i1=fen(l),i2=fen(r+1);
while(i1!=i2){
i1->v+=v;++i1;
}
}
inline void tui(int l,int r,int v){
auto i1=fen(l),i2=fen(r+1);
i1->r=r,i1->v=v;
s.erase(++i1,i2);
}
inline int kth(int l,int r,int x){
auto i1=fen(l),i2=fen(r+1);
vector<pii> v;
while(i1!=i2)v.ps({i1->r-i1->l+1,i1->v}),++i1;
sort(v.begin(),v.end(),[](const pii x,const pii y){return x.se<y.se;});
for(auto i:v){
if(x<=i.fi)return i.se;
x-=i.fi;
}
}
inline int qpow(int x,int y,int mo){
int ans=1;x%=mo;
while(y){
if(y&1)ans=ans*x%mo;
x=x*x%mo,y>>=1;
}
return ans;
}
inline int ask(int l,int r,int x,int y){
auto i1=fen(l),i2=fen(r+1);
int ans=0;
while(i1!=i2)ans=(ans+qpow(i1->v,x,y)*(i1->r-i1->l+1))%y,++i1;
return ans;
}
signed main(){
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
n=read(),m=read(),sd=read(),vmax=read();
for(int i=1;i<=n;++i)
s.insert({i,i,(rd()%vmax)+1});
for(int i=1,op,l,r,x,y;i<=m;++i){
op=(rd()%4)+1,l=(rd()%n)+1,r=(rd()%n)+1;
if(l>r)swap(l,r);
if(op==3)x=(rd()%(r-l+1))+1;
else x=(rd()%vmax)+1;
if(op==4)y=(rd()%vmax)+1;
if(op==1)add(l,r,x);
else if(op==2)tui(l,r,x);
else if(op==3)cout<<kth(l,r,x)<<'\n';
else cout<<ask(l,r,x,y)<<'\n';
}
}
那么这个东西是什么复杂度呢,详见 珂朵莉树/颜色段均摊 ,其实我根本不会他的证明,一直把他当卡常来用,一般有推平和随机的题就用就行了。
建议做题顺序
2->1->3->5->4

浙公网安备 33010602011771号