分块
分块
分块是一种优雅的暴力。分块就是暴力。
支持大部分的区间操作(包括线段树不能做的),但是复杂度比线段树的 \(O(N\log N)\) 更劣,时间复杂度 \(O(N\sqrt N)\)。
把一个长度为 \(N\) 的序列分成长度为 \(\sqrt N\) 的 \(\sqrt N\) 个块。
对一个区间 \([L,R]\) 操作时,可以将这个区间分成两端的不完整的块和中间的至多 \(\sqrt N\) 的块。
对于不完整的块,可以直接暴力跑每个点,复杂度 \(O(\sqrt N)\)。
对于中间完整的块,块中的所有元素肯定都是要被操作的,可以像线段树那样打一个懒标记,等到真正遍历到那些点的时候再操作。
总时间复杂度 \(O(N\sqrt N)\)。
例题:P13977 数列分块入门 2
用两个数组,一个维护原序列,一个维护一个块中的有序序列。
查询不完整块的时候直接暴力跑,查询完整块的时候在块中二分。
时间复杂度是 \(O(N\sqrt N\log\sqrt N)\)
#include<bits/stdc++.h>
#define int long long
//#define lc p<<1
//#define rc p<<1|1
#define endl putchar('\n')
#define psp putchar(' ')
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=3e5+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
void putstr(string s){
for(int i=0;i<s.size();i++)putchar(s[i]);
}
int lowbit(int x){
return x&-x;
}
int n,m,k;
int T;
int a[N];
int b[N];
int len;
int cnt;
int L[N],R[N];
int belong[N];
int add[N];
void init(){
for(int i=1;i<=n;i+=len){
cnt++;
L[cnt]=i,R[cnt]=min(n,i+len-1);
for(int j=i;j<=i+len-1&&j<=n;j++){
belong[j]=cnt;
b[j]=a[j];
}
sort(b+L[cnt],b+R[cnt]+1);
}
}
void update(int l,int r,int c){
if(belong[l]==belong[r]){
for(int i=l;i<=r;i++)a[i]+=c;
for(int i=L[belong[l]];i<=R[belong[l]];i++)b[i]=a[i];
sort(b+L[belong[l]],b+R[belong[l]]+1);
}
else{
for(int i=l;i<=R[belong[l]];i++)a[i]+=c;
for(int i=L[belong[l]];i<=R[belong[l]];i++)b[i]=a[i];
sort(b+L[belong[l]],b+R[belong[l]]+1);
for(int i=L[belong[r]];i<=r;i++)a[i]+=c;
for(int i=L[belong[r]];i<=R[belong[r]];i++)b[i]=a[i];
sort(b+L[belong[r]],b+R[belong[r]]+1);
for(int i=belong[l]+1;i<=belong[r]-1;i++){
add[i]+=c;
}
}
}
int query(int l,int r,int c){
int res=0;
if(belong[l]==belong[r]){
for(int i=l;i<=r;i++)res+=(a[i]+add[belong[l]]<c);
}
else{
for(int i=l;i<=R[belong[l]];i++)res+=(a[i]+add[belong[l]]<c);
for(int i=L[belong[r]];i<=r;i++)res+=(a[i]+add[belong[r]]<c);
for(int i=belong[l]+1;i<=belong[r]-1;i++){
int RR=lower_bound(b+L[i],b+R[i]+1,c-add[i])-b;
if(RR<L[i])continue;
res+=RR-L[i];
}
}
return res;
}
signed main(){
//ios::sync_with_stdio(0);
n=read();
for(int i=1;i<=n;i++)a[i]=read();
len=sqrt(n);
init();
for(int i=1;i<=n;i++){
int op=read(),l=read(),r=read(),c=read();
if(op==0){
update(l,r,c);
}
else{
print(query(l,r,c*c)),endl;
}
}
}
块状链表
用来维护一个序列(插入元素,删除元素)。
前面的分块实现的功能都不包括元素的增删。
除了平衡树以外,还有一个东西能维护序列——链表。
链表的优点是插入 \(O(1)\),但是查找一个元素需要 \(O(N)\),可以利用分块的思想将两个操作的时间复杂度均衡一下。
将原本链表中的一个元素改成一个块,快的长度是 \(\sqrt N\),所以总的块数就是 \(\sqrt N\) 个。这样插入和删除都是 \(O(\sqrt N)\) 的。
例题:P4008 [NOI2003] 文本编辑器
这道题最好写成迭代的版本,递归会炸。
#include<bits/stdc++.h>
//#define lc p<<1
//#define rc p<<1|1
#define endl putchar('\n')
#define psp putchar(' ')
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=1.5e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
void putstr(string s){
for(int i=0;i<s.size();i++)putchar(s[i]);
}
int lowbit(int x){
return x&-x;
}
int n,m,k;
int T;
int len;
struct node{
int pre;
int nex;
vector<char>num;
}f[N];
int cnt=1;
int a[N];
int cur;
string s;
int tot;
int dirt[N];
int new_block(){
if(tot){
int num=dirt[tot--];
f[num]=node{0,0};
f[num].num.clear();
return num;
}
return ++cnt;
}
pair<int,int>find(int pos){
int p=0;
int sz=0;
while(1){
p=f[p].nex;
sz+=f[p].num.size();
if(sz>=pos)break;
}
sz-=f[p].num.size();
return {p,pos-sz-1};
}
void insert(int p,int id,int num){
while(1){
char c=getchar();
while(c=='\n'||c=='\r')c=getchar();
f[p].num.insert(f[p].num.begin()+1+id,c);
num--;
if(!num)return;
if(f[p].num.size()>2*len){
int cnt=new_block();
for(int i=len;i<f[p].num.size();i++)f[cnt].num.push_back(f[p].num[i]);
while(f[p].num.size()>len)f[p].num.pop_back();
f[cnt].pre=p;
f[cnt].nex=f[p].nex;
if(f[p].nex)f[f[p].nex].pre=cnt;
f[p].nex=cnt;
if(id+1>=f[p].num.size()){
id-=f[p].num.size();
p=cnt;
}
}
id++;
}
}
void del(int p,int id,int num){
while(1){
int has=f[p].num.size()-id;
if(has<=num){
if(has==f[p].num.size()){
f[f[p].pre].nex=f[p].nex;
f[f[p].nex].pre=f[p].pre;
num-=f[p].num.size();
int nex=f[p].nex;
dirt[++tot]=p;
if(!num){
if(f[0].nex==0){
int nn=new_block();
f[0].nex=nn;
}
return;
}
p=nex;
}
else{
while(has--){
f[p].num.pop_back();
num--;
}
if(!num)return;
p=f[p].nex;
}
id=0;
}
else{
vector<char>new_node;
for(int i=0;i<id;i++)new_node.push_back(f[p].num[i]);
for(int i=id+num;i<f[p].num.size();i++)new_node.push_back(f[p].num[i]);
f[p].num=new_node;
new_node.clear();
return;
}
}
}
void output(int p,int id,int num){
while(1){
cout<<f[p].num[id];
num--;
if(!num)return;
id++;
if(id==f[p].num.size())p=f[p].nex,id=0;
}
}
signed main(){
// freopen("P4008_1.in","r",stdin);
// freopen("fifi.txt","w",stdout);
//ios::sync_with_stdio(0);
f[0].nex=1;
cin>>m;
len=sqrt(m);
while(m--){
string op;
cin>>op;
if(op=="Move"){
cin>>cur;
}
else if(op=="Insert"){
pair<int,int>ccc=find(cur);
cin>>n;
// cin>>ws;
// getline(cin,s);
// reverse(s.begin(),s.end());
insert(ccc.first,ccc.second,n);
}
else if(op=="Delete"){
pair<int,int>ccc=find(cur+1);
cin>>n;
del(ccc.first,ccc.second,n);
}
else if(op=="Get"){
pair<int,int>ccc=find(cur+1);
cin>>n;
output(ccc.first,ccc.second,n);
cout<<'\n';
}
else if(op=="Prev"){
cur--;
}
else{
cur++;
}
}
}
根号分治
适用于:
一个题有两种做法(暴力方法),一种是查询 \(O(1)\),修改 \(O(N)\),一种是查询 \(O(N)\),修改 \(O(1)\)。
我们需要找到一个阈值 \(B\),当 \(x<B\) 时,第一种方法更快。当 \(x>B\) 时,第二种方法更快。
比如:P3396 哈希冲突
两种方法:
- 暴力查找答案,修改直接修改原数组。
- 预处理所有情况,查询直接访问数组,修改要改 \(n\) 个元素。
当输入的 \(x\) 较小时,答案分布更密集,更多,明显预处理更优,但一次修改的复杂度是 \(O(B)\)。
当输入的 \(x\) 较大时,答案分布更稀疏,更少,暴力就快一点,查询复杂度 \(O(\frac{N}{B})\)。
要使整体的复杂度更优,就要两个情况的复杂度更接近,即 \(B=\frac{N}{B}\),那么 \(B=\sqrt N\)。
单次操作复杂度 \(O(\sqrt N)\),总复杂度 \(O(M\sqrt N)\)。
#include<bits/stdc++.h>
//#define int long long
//#define lc p<<1
//#define rc p<<1|1
#define endl putchar('\n')
#define psp putchar(' ')
using namespace std;
const int N=1e6+5;
const int M=1e3+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
void putstr(string s){
for(int i=0;i<s.size();i++)putchar(s[i]);
}
int lowbit(int x){
return x&-x;
}
int n,m,k;
int T;
int a[N];
int mod[M][M];
signed main(){
ios::sync_with_stdio(0);
cin>>n>>m;
int len=sqrt(n);
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=len;i++){
for(int j=1;j<=n;j++){
mod[i][j%i]+=a[j];
}
}
while(m--){
char op;
int x,y;
cin>>op>>x>>y;
if(op=='A'){
if(x<=len){
cout<<mod[x][y]<<'\n';
}
else{
int res=0;
for(int i=y;i<=n;i+=x)res+=a[i];
cout<<res<<'\n';
}
}
else{
for(int i=1;i<=len;i++)mod[i][x%i]-=a[x];
a[x]=y;
for(int i=1;i<=len;i++)mod[i][x%i]+=a[x];
}
}
}
浙公网安备 33010602011771号