分块介绍
有一种代码比树状数组、线段树简单,效率比暴力法高的算法,称为“分块”,它能以 O ( m ∗ n 0.5 ) {O(m*n^{0.5})} O(m∗n0.5)的复杂度解决“区间修改+区间查询”问题。简单地说,分块是用线段树的“分区”思想改良的暴力法;它把数列分成很多“块”,对涉及到的块做整体性的维护操作(类似于线段树的lazy-tag),而不是像普通暴力法那样处理整个数列,从而提高了效率。用一个长度为n的数组来存储 n {n} n个数据,把它分为 t {t} t块,每块长度为 n / t {n/t} n/t .
分块处理思想
在处理区间问题时需要处理两种块:整块(中间)和碎块(两端).此时整块整体处理,而碎块用暴力处理即可,单次复杂度 O ( n 0.5 ) {O(n^{0.5})} O(n0.5),处理 1 0 5 {10^5} 105级的数据绰绰有余(加常数优化效果更佳),在暴力算法中很可观.
分块模板
线段树分块模板
(数据小,可以用它搞qwq)
#include<bits/stdc++.h>
#define N 200005
#define int long long
#define in read()
using namespace std;
int n,m,t,block,st[N],ed[N],pos[N];
int a[N],sum[N],add[N];
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch-'0');ch=getchar();}
return i*f;
}
inline void Init()//分块核心
{
block=sqrt(n);
t=n/block;
if(n%block)t++;
for(int i=1;i<=t;i++)
{
st[i]=(i-1)*block+1;
ed[i]=i*block;
}
ed[t]=n;
for(int i=1;i<=n;i++)
pos[i]=(i-1)/block+1;
for(int i=1;i<=t;i++)
for(int j=st[i];j<=ed[i];j++)
sum[i]+=a[j];//预处理区间和
return;
}
inline void addx(int l,int r,int d)//区间加
int p=pos[l],q=pos[r];
if(p==q)
{
for(int i=l;i<=r;i++)a[i]+=d;
sum[p]+=d*(r-l+1);
}
else
{
for(int i=p+1;i<=q-1;i++)add[i]+=d;
for(int i=l;i<=ed[p];i++)a[i]+=d;
sum[p]+=d*(ed[p]-l+1);
for(int i=st[q];i<=r;i++)a[i]+=d;
sum[q]+=d*(r-st[q]+1);
}
return;
}
inline int query(int l,int r)//查询
{
int p=pos[l],q=pos[r],ans=0;
if(p==q)
{
for(int i=l;i<=r;i++)ans+=a[i];
ans+=add[p]*(r-l+1);
}
else
{
for(int i=p+1;i<=q-1;i++)ans+=sum[i]+add[i]*(ed[i]-st[i]+1);
for(int i=l;i<=ed[p];i++)ans+=a[i];
ans+=add[p]*(ed[p]-l+1);
for(int i=st[q];i<=r;i++)ans+=a[i];
ans+=add[q]*(r-st[q]+1);
}
return ans;
}
signed main()
{
int x,y,z;
n=in,m=in;;
for(int i=1;i<=n;i++)
a[i]=in;
Init();
for(int i=1;i<=m;i++)
{
int opt=in;
if(opt==1)
{
x=in,y=in,z=in;
addx(x,y,z);
}
if(opt==2)
{
x=in,y=in;
printf("%lld\n",query(x,y));
}
}
return 0;
}
接下来是例题.
1.教主的魔法
这道题就是用一个无序数组
b
[
]
{b[]}
b[]维护各个块中每个数从小到大保持有序+二分查找即可.
时间复杂度:
O
(
n
+
m
∗
(
n
0.5
l
o
g
n
0.5
+
l
o
g
n
0.5
)
≈
n
1.5
l
o
g
n
0.5
)
{O(n+m*(n^{0.5}log~n^{0.5}+log~n^{0.5})≈n^{1.5}logn^{0.5})}
O(n+m∗(n0.5log n0.5+log n0.5)≈n1.5logn0.5)
附上代码:
#include<bits/stdc++.h>
#define N 2000005
#define int long long
#define in read()
using namespace std;
int n,m,t,block,st[N],ed[N],pos[N];
int a[N],b[N],add[N];
inline int in{
int i=0,f=1;char ch;
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch-'0');ch=getchar();}
return i*f;
}
inline void Init()
{
block=sqrt(n);
t=n/block;
if(n%block)t++;
for(int i=1;i<=t;i++)
{
st[i]=(i-1)*block+1;
ed[i]=i*block;
}
ed[t]=n;
for(int i=1;i<=n;i++)
pos[i]=(i-1)/block+1;
for(int i=1;i<=t;i++)
sort(b+st[i],b+ed[i]+1);
return;
}
inline void addx(int l,int r,int d)
{
int p=pos[l],q=pos[r];
if(p==q)
{
for(int i=st[p];i<=ed[p];i++)
{
if(i>=l&&i<=r)a[i]+=d,
b[i]=a[i];
}
sort(b+st[p],b+ed[p]+1);
}
else
{
for(int i=p+1;i<=q-1;i++)add[i]+=d;
for(int i=st[p];i<=ed[p];i++)
{
if(i>=l)a[i]+=d;
b[i]=a[i];
}
sort(b+st[p],b+ed[p]+1);
for(int i=st[q];i<=ed[q];i++)
{
if(i<=r)a[i]+=d;
b[i]=a[i];
}
sort(b+st[q],b+ed[q]+1);
}
return;
}
inline int query(int l,int r,int d)
{
int p=pos[l],q=pos[r],ans=0;
if(l==r)return (b[l]>=d-add[p]? 1 : 0);
if(p==q)
{
int ll=st[p],rr=ed[p]+1;
while(ll<rr)
{
int mi=(ll+rr)>>1;
if(b[mi]>=d-add[p])rr=mi;
else ll=mi+1;
}
ans+=r-rr+1;
}
else
{
for(int i=p+1;i<=q-1;i++)
{
int ll=st[i],rr=ed[i]+1;
while(ll<rr)
{
int mi=(ll+rr)>>1;
if(b[mi]>=d-add[i])rr=mi;
else ll=mi+1;
}
ans+=ed[i]-rr+1;
}
for(int i=l;i<=ed[p];i++)
if(b[i]>=d-add[p])ans++;
for(int i=st[q];i<=r;i++)
if(b[i]>=d-add[q])ans++;
}
return ans;
}
signed main()
{
// freopen("教主的魔法.in","r",stdin);
int x,y,z;
n=in,m=in;
for(int i=1;i<=n;i++)
{
a[i]=in;
b[i]=a[i];
}
Init();
for(int i=1;i<=m;i++)
{
char opt;
cin>>opt;
if(opt=='M')
{
x=in,y=in,z=in;
addx(x,y,z);
// for(int i=1;i<=n;i++)
// cout<<b[i]+add[pos[i]]<<" ";
// cout<<endl;
}
if(opt=='A')
{
x=in,y=in,z=in;
printf("%lld\n",query(x,y,z));
}
}
return 0;
}
2.弹飞绵羊
这道题需要用两个数组
t
o
[
i
]
{to[i]}
to[i]和
s
t
e
p
[
i
]
{step[i]}
step[i]分别表示从
i
{i}
i开始弹到下一个块的第一个点的位置以及所需的次数.更新时由于每个块互不影响,所以单次更新只需对所更新的弹簧所在的块进行更新即可.对于查询,即只需进行模拟,并利用已处理的数组进行一次一个区间的快速跳跃。
时间复杂度:
O
(
m
n
0.5
)
{O(mn^{0.5})}
O(mn0.5).
#include<bits/stdc++.h>
#define N 200005
#define in read()
#define re register
using namespace std;
int n,m,a[N],ans;
int block,t,st[N],ed[N],to[N],stp[N],pos[N];
int jn1[N];
inline int in{
int i=0;char ch;
while(!isdigit(ch)){ch=getchar();}
while(isdigit(ch)){i=(i<<3)+(i<<1)+(ch-'0');ch=getchar();}
return i;
}
inline void Init()
{
block=sqrt(n);
t=n/block;
if(n%block)t++;
for(re int i=1;i<=t;i++){
st[i]=(i-1)*block+1;
ed[i]=i*block;
}
ed[t]=n;
for(re int i=1;i<=n;i++)
pos[i]=(i-1)/block+1;
}
inline void update(int l,int r)
{
for(re int i=r;i>=l;i--){
if(i+a[i]>ed[pos[i]]){
stp[i]=1,to[i]=i+a[i];}
else{
stp[i]=stp[i+a[i]]+1,to[i]=to[i+a[i]];}
}
return;
}
inline int work(int x)
{
int ans=0;
while(x<=n){
ans+=stp[x];
x=to[x];
}
return ans;
}
int main()
{
// freopen("弹飞绵羊.in","r",stdin);
// freopen("弹飞绵羊.out","w",stdout);
n=in;
for(re int i=1;i<=n;i++)
a[i]=in;
Init();
update(1,n);
m=in;
for(re int i=1;i<=m;i++){
int x,y,z;
x=in,y=in,y++;
if(x==1)
cout<<work(y)<<endl;
else{
z=in,a[y]=z;
update(st[pos[y]],ed[pos[y]]);
}
}
return 0;
}
完结撒花qwq.