分块相关题目
题单。
UPD:题单里的题
数列分块入门一
看到区间修改
考虑分块维护差分数组。对于修改操作,就对
时间复杂度
代码。
数列分块入门二
如果直接查找,那么复杂度是线性的,因此我们考虑让原序列有序。
一个简单的思路;一开始将每个块排序,然后修改整块打标记,散块暴力修改。对于查询操作,散块暴力统计,整块直接二分。
可惜这样是错的。
原因很简单,排序会破坏原数组的顺序,这样散块的结果就不对了。
考虑设辅助数组
注意整块二分的时候要先减去该块的标记再对
时间复杂度
代码。
更优的做法:对每个块排序,记录每个元素原来的位置。然后对零散块的修改就可以直接归并,砍掉一只
然后根号平衡随便算算可知块长取
块长取常数!!!
代码。
类似的题:P2801 教主的魔法。只需要改改数据范围,开 long long 即可。
数列分块入门三
仍然可以像上个题一样二分查找。
但是还有一种思路:对每个块维护一个 multiset
。修改时,整块打标记,散块在 multiset
中删除原数 lower_bound-1
),散块暴力。
时间复杂度
代码。
数列分块入门四
考虑维护每个块的和。
对于修改操作,整块打标记,散块暴力修改;对于查询操作,整块加上
时间复杂度
代码。
数列分块入门五
乍一看好像不好维护,考虑挖掘一下开方的性质。
实际上,对于
对于查询操作,只需要在修改的同时顺便维护下区间和即可。
时间复杂度
代码。
数列分块入门六
块状链表板子。
我们考虑维护这么一个数据结构:内部是一个不定长的块,并设置一个最大长度 list<vector<int> >
。
接下来考虑实现如下操作:
零、定义
list<vector<int> > List;
typedef list<vector<int> >::iterator IT;
一、初始化
由于这个题一开始有 vector
中并插入块状链表即可。
vector<int>a;
for(int i=1;i<=n;++i){
int x=read();
a.emplace_back(x);
}
List.insert(List.end(),a);
关于 std::list
的 insert
函数请自行 BDFS。
二、查找某个位置所在块
在对某个位置的元素进行操作时,往往需要求出它处于哪个块中,并更新它为在其所在块中的位置,便于在块中进行访问。
做法:枚举每一个块计算 size
即可。
inline IT find(int &pos){
// 返回 pos 所在块,pos 变为在其所在块的位置
for(IT i=List.begin();;++i){
if(i==List.end()||pos<=(int)i->size())return i;
pos-=i->size();
}
}
三、查询某一位置的值
我们假定下标从
我们首先调用 find
函数求出当前位置所在的块 it
及其在所在块的位置 pos
。由于容器的下标是从 it->at(pos-1)
。
inline int get(int pos){
// 查询第 pos 个元素的值
IT it=find(pos);
return it->at(pos-1);
}
四、后继
查找某个块的后继。
指针
inline IT Next(IT x){return ++x;}
五、合并
假设要合并
我们只需要将
inline void Merge(IT x){
// merge x and x+1
x->insert(x->end(),Next(x)->begin(),Next(x)->end());
List.erase(Next(x));
}
六、分裂
这次我们要将块
首先特判一下
然后把
inline void Split(IT x,int pos){
// 将第 x 块从第 pos 个元素后面分开
if(pos==(int)x->size())return;
List.insert(Next(x),vector<int>(x->begin()+pos,x->end()));
x->erase(x->begin()+pos,x->end());
}
七、重构
这个是重点。
由于频繁插入后可能会使一个块的大小过大,从而浪费时间,因此需要将这些块重新分裂、合并。
具体操作是,扫描每一个块,如果其大小超过
一次重构的最坏复杂度是
inline void rebuild(){
// 重构
for(IT i=List.begin();i!=List.end();++i){
while(i->size()>=(LEN<<1)) Split(i,i->size()-LEN);
while(Next(i)!=List.end()&&i->size()+Next(i)->size()<=LEN) Merge(i);
while(Next(i)!=List.end()&&!Next(i)->size()) List.erase(Next(i));
}
}
八、插入元素
插入分为在前面插入和在后面插入两种。本题为在前面插入,但实际上在第
首先将第 vector
)。
inline void insert(int pos,int x){
// 在 pos 前面插入 x
pos--;
IT now=find(pos);
if(List.size())Split(now,pos);
List.insert(Next(now),vector<int>(1,x));
rebuild();
}
这个是在前面插入的,在后面插入把 pos--
去掉即可。
然后这个题需要维护的操作就没了,注意初始化的时候也要重构一次。
代码(output
为 debug
)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」