- 参考资料:
「分块」数列分块入门1 – 9 by hzwer
分块入门1~9 - 什么是分块?
- 分块就是将一段序列,分成若干个块,再进行暴力。
数列分块入门 1
-
给定一个长度为 n n n的序列,有 n n n个操作,区间加法和单点询问
-
将序列分成 s q r t ( n ) sqrt(n) sqrt(n)个块
-
对于一段区间修改,分两种情况讨论:
-
如果修改区间在一个块中,则暴力修改 a [ i ] a[i] a[i]的值
-
如果修改区间不在一个块中:
对于最左边和最右边的块,暴力修改
中间的整块维护一个块的 l a z y lazy lazy值 -
时间复杂度 O ( ( n / m ) + m ) O((n/m)+m) O((n/m)+m),根据均值不等式, m = s q r t ( n ) m=sqrt(n) m=sqrt(n)时最优
#include <bits/stdc++.h>
using namespace std;
#define maxn 50010
int n,a[maxn],L[maxn],R[maxn],t,pos[maxn],la[maxn];
inline 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_<<1)+(x_<<3)+c_-'0';c_=getchar();}
return x_*f_;
}
inline void update_(int l,int r,int w) {
int LL=pos[l],RR=pos[r];
if(LL==RR) for(int i=l;i<=r;++i) a[i]+=w;
else {
for(int i=l;i<=R[LL];++i) a[i]+=w;
for(int i=L[RR];i<=r;++i) a[i]+=w;
for(int i=LL+1;i<=RR-1;++i) la[i]+=w;
}
}
void readda_() {
n=read_();
for(int i=1;i<=n;++i) a[i]=read_();
t=sqrt(n);//块的大小亦块数,t*t=n
for(int i=1;i<=t;++i) {
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if(R[t]<n) {++t;L[t]=R[t-1]+1;R[t]=n;}
for(int i=1;i<=t;++i) {
for(int j=L[i];j<=R[i];++j) {
pos[j]=i;//每个点属于哪个块
}
}
for(int i=1;i<=n;++i) {
int pd=read_(),l=read_(),r=read_(),c=read_();
if(!pd) update_(l,r,c);
else printf("%d\n",a[r]+la[pos[r]]);
}
}
int main() {
freopen("a.txt","r",stdin);
readda_();
return 0;
}
数列分块入门 2
-
给出一个长为 n n n的数列,以及 n n n个操作,操作涉及区间加法,询问区间内小于某个值 x x x的元素个数。
-
要快速查询一个区间小于 x x x的数有多少,在区间有序的情况下我们可以使用二分 l o w e r lower lower_ b o u n d bound bound
-
维护出块之后,给每个块内排序,使块内元素有序
-
考虑修改:
如果一整个块都加上一个数,不影响块内元素的相对大小
如果修改一个块内的部分的数,则有可能会影响相对大小,在修改完之后,重新排序 -
考虑询问:
如果询问一整个块的话使用二分
否则暴力询问
#include <bits/stdc++.h>
using namespace std;
#define maxn 50010
#define maxt 225
int n,a[maxn],L[maxt],R[maxt],pos[maxn],t,la[maxt];
vector <int >e[maxt];
inline 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_<<1)+(x_<<3)+c_-'0';c_=getchar();}
return x_*f_;
}
inline void resert_(int x) {
e[x].clear();
for(int i=L[x];i<=R[x];++i) e[x].push_back(a[i]);
sort(e[x].begin(),e[x].end());
}
inline void update_(int l,int r,int w) {
int LL=pos[l],RR=pos[r];
if(LL==RR) {
for(int i=l;i<=r;++i) a[i]+=w;
resert_(LL);
}
else {
for(int i=l;i<=R[LL];++i) a[i]+=w;
resert_(LL);
for(int i=L[RR];i<=r;++i) a[i]+=w;
resert_(RR);
for(int i=LL+1;i<=RR-1;++i) la[i]+=w;
}
}
inline int query_(int l,int r,int w) {
int LL=pos[l],RR=pos[r];
int ans=0;
if(LL==RR) {
for(int i=l;i<=r;++i) {
if(a[i]+la[pos[i]]<w) ++ans;
}
}
else {
for(int i=l;i<=R[LL];++i) {
if(a[i]+la[pos[i]]<w) ++ans;
}
for(int i=L[RR];i<=r;++i) {
if(a[i]+la[pos[i]]<w) ++ans;
}
for(int i=LL+1;i<=RR-1;++i) {
ans+=lower_bound(e[i].begin(),e[i].end(),w-la[i])-e[i].begin();
}
}
return ans;
}
void readda_() {
n=read_();
for(int i=1;i<=n;++i) a[i]=read_();
t=sqrt(n);
for(int i=1;i<=t;++i) {
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if(R[t]<n) {++t;L[t]=R[t-1]+1;R[t]=n;}
for(int i=1;i<=t;++i) {
for(int j=L[i];j<=R[i];++j) {
pos[j]=i;
e[i].push_back(a[j]);
}
sort(e[i].begin(),e[i].end());
}
for(int i=1;i<=n;++i) {
int pd=read_(),l=read_(),r=read_(),c=read_();
if(!pd) update_(l,r,c);
else printf("%d\n",query_(l,r,c*c));
}
}
int main() {
freopen("a.txt","r",stdin);
readda_();
return 0;
}
数列分块入门 3
- 给出一个长为 n n n的数列,以及 n n n个操作,操作涉及区间加法,询问区间内小于某个值 x x x的前驱(比其小的最大元素)。
- 和上一题差不多,使块内的元素单调,可以使用二分查询 x x x的位置, x x x的前驱就是其前面那个
- 我们开个 v i s [ i ] vis[i] vis[i]表示 i i i号块是否被暴力修改,在查询时再去维护其单调性,不用修改了就去维护,有可能询问区间不涉及暴力修改区间
#include <bits/stdc++.h>
using namespace std;
#define maxn 100010
#define maxt 330
int t,n,a[maxn],L[maxt],R[maxt],pos[maxn],la[maxt];
bool vis[maxt];
vector <int > e[maxt];
inline 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_<<1)+(x_<<3)+c_-'0';c_=getchar();}
return x_*f_;
}
inline void resert_(int x) {
e[x].clear();
for(int i=L[x];i<=R[x];++i) e[x].push_back(a[i]);
sort(e[x].begin(),e[x].end());
vis[x]=0;
}
inline void update_(int l,int r,int w) {
int LL=pos[l],RR=pos[r];
if(LL==RR) {
for(int i=l;i<=r;++i) a[i]+=w;
vis[LL]=1;
}
else {
for(int i=l;i<=R[LL];++i) a[i]+=w;
vis[LL]=1;
for(int i=L[RR];i<=r;++i) a[i]+=w;
vis[RR]=1;
for(int i=LL+1;i<=RR-1;++i) la[i]+=w;
}
}
inline int query_(int l,int r,int w) {
int LL=pos[l],RR=pos[r];
if(LL==RR) {
int ans=-1;
for(int i=l;i<=r;++i) {
if(a[i]+la[pos[i]]<w) {
ans=max(ans,a[i]+la[pos[i]]);
}
}
return ans;
}
else {
int ans=-1;
for(int i=l;i<=R[LL];++i) {
if(a[i]+la[pos[i]]<w) {
ans=max(ans,a[i]+la[pos[i]]);
}
}
for(int i=L[RR];i<=r;++i) {
if(a[i]+la[pos[i]]<w) {
ans=max(ans,a[i]+la[pos[i]]);
}
}
for(int i=LL+1;i<=RR-1;++i) {
if(vis[i]) resert_(i);
int AKIOI=lower_bound(e[i].begin(),e[i].end(),w-la[i])-e[i].begin();
if(!AKIOI) continue;
ans=max(ans,e[i][AKIOI-1]+la[i]);
}
return ans;
}
}
void readda_() {
n=read_();
for(int i=1;i<=n;++i) a[i]=read_();
t=sqrt(n);
for(int i=1;i<=t;++i) {
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if(R[t]<n) {++t;L[t]=R[t-1]+1;R[t]=n;}
for(int i=1;i<=t;++i) {
for(int j=L[i];j<=R[i];++j) {
pos[j]=i;
e[i].push_back(a[j]);
}
sort(e[i].begin(),e[i].end());
}
for(int i=1;i<=n;++i) {
int pd=read_(),l=read_(),r=read_(),c=read_();
if(!pd) update_(l,r,c);
else printf("%d\n",query_(l,r,c));
}
}
int main() {
freopen("a.txt","r",stdin);
readda_();
return 0;
}
- 当然,这个题想表达的思想是块内又可以用另一种数据结构来实现,所以块内部可以用 s e t set set来实现
#include <bits/stdc++.h>
using namespace std;
#define maxn 100010
#define maxt 330
int n,L[maxt],R[maxt],la[maxt],a[maxn],t,pos[maxn];
set <int > e[maxt];
inline 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_<<1)+(x_<<3)+c_-'0';c_=getchar();}
return x_*f_;
}
inline void update_(int l,int r,int w) {
int LL=pos[l],RR=pos[r];
if(LL==RR) {
for(int i=l;i<=r;++i) {
e[LL].erase(a[i]);
a[i]+=w;
e[LL].insert(a[i]);
}
}
else {
for(int i=l;i<=R[LL];++i) {
e[LL].erase(a[i]);
a[i]+=w;
e[LL].insert(a[i]);
}
for(int i=L[RR];i<=r;++i) {
e[RR].erase(a[i]);
a[i]+=w;
e[RR].insert(a[i]);
}
for(int i=LL+1;i<=RR-1;++i) la[i]+=w;
}
}
inline int query_(int l,int r,int w) {
int LL=pos[l],RR=pos[r];
if(LL==RR) {
int ans=-1,AKIOI;
for(int i=l;i<=r;++i) {
AKIOI=a[i]+la[LL];
if(AKIOI<w) ans=max(ans,AKIOI);
}
return ans;
}
else {
int ans=-1,AKIOI;
for(int i=l;i<=R[LL];++i) {
AKIOI=a[i]+la[LL];
if(AKIOI<w) ans=max(ans,AKIOI);
}
for(int i=L[RR];i<=r;++i) {
AKIOI=a[i]+la[RR];
if(AKIOI<w) ans=max(ans,AKIOI);
}
for(int i=LL+1;i<=RR-1;++i) {
set<int> :: iterator it=e[i].lower_bound(w-la[i]);
if(it==e[i].begin()) continue;
--it;
ans=max(ans,*it+la[i]);
}
return ans;
}
}
void readda_() {
n=read_();
for(int i=1;i<=n;++i) a[i]=read_();
t=sqrt(n);
for(int i=1;i<=t;++i) {
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if(R[t]<n) {++t;L[t]=R[t-1]+1;R[t]=n;}
for(int i=1;i<=t;++i) {
for(int j=L[i];j<=R[i];++j) {
pos[j]=i;
e[i].insert(a[j]);
}
}
for(int i=1;i<=n;++i) {
int pd=read_(),l=read_(),r=read_(),c=read_();
if(!pd) update_(l,r,c);
else printf("%d\n",query_(l,r,c));
}
}
int main() {
freopen("a.txt","r",stdin);
readda_();
return 0;
}
数列分块入门 4
- 给出一个长为 n n n的数列,以及 n n n个操作,操作涉及区间加法,区间求和。
- 每个块维护一个块的总和 s u m [ i ] sum[i] sum[i]
- 修改时,不完整块暴力修改 a 和 s u m a和sum a和sum,完整块修改 l a z y lazy lazy
- 询问时,不完整块暴力询问,完整块询问和,注意加上 l a z y ∗ 区 间 长 度 lazy*区间长度 lazy∗区间长度
- 要开 l o n g long long l o n g long long
#include <bits/stdc++.h>
using namespace std;
#define maxn 50010
#define maxt 240
int n,L[maxt],R[maxt],pos[maxn],t;
long long a[maxn],sum[maxt],la[maxt];
inline long long read_() {
long long 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_<<1)+(x_<<3)+c_-'0';c_=getchar();}
return x_*f_;
}
inline void update_(int l,int r,int w) {
int LL=pos[l],RR=pos[r];
if(LL==RR) {
for(int i=l;i<=r;++i) {
a[i]+=w;
sum[LL]+=w;
}
}
else {
for(int i=l;i<=R[LL];++i) {
a[i]+=w;
sum[LL]+=w;
}
for(int i=L[RR];i<=r;++i) {
a[i]+=w;
sum[RR]+=w;
}
for(int i=LL+1;i<=RR-1;++i) la[i]+=w;
}
}
inline long long query_(int l,int r) {
int LL=pos[l],RR=pos[r];
long long ans=0;
if(LL==RR) {
for(int i=l;i<=r;++i) ans+=a[i];
ans+=(r-l+1)*la[LL];
}
else {
for(int i=l;i<=R[LL];++i) ans+=a[i];
ans+=(R[LL]-l+1)*la[LL];
for(int i=L[RR];i<=r;++i) ans+=a[i];
ans+=(r-L[RR]+1)*la[RR];
for(int i=LL+1;i<=RR-1;++i) {
ans+=sum[i];
ans+=(R[i]-L[i]+1)*la[i];
}
}
return ans;
}
void readda_() {
n=read_();
for(int i=1;i<=n;++i) a[i]=read_();
t=sqrt(n);
for(int i=1;i<=t;++i) {
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if(R[t]<n) {++t;L[t]=R[t-1]+1;R[t]=n;}
for(int i=1;i<=t;++i) {
for(int j=L[i];j<=R[i];++j) {
pos[j]=i;
sum[i]+=a[j];
}
}
for(int i=1;i<=n;++i) {
int pd=read_(),l=read_(),r=read_(),c=read_();
if(!pd) update_(l,r,c);
else printf("%lld\n",query_(l,r)%(c+1));
}
}
int main() {
freopen("a.txt","r",stdin);
readda_();
return 0;
}
数列分块入门 5
- 给出一个长为 n n n的数列 ,以及 n n n个操作,操作涉及区间开方,区间求和。
- 修改:不是完整的块暴力修改,对于完整的块?我们也只能暴力修改,但如果块内元素都小于等于 1 1 1,就不用去修改了,而一个很大的数开方几次就到 1 1 1了,所以复杂度是能承受的。
- 询问:不完整的块暴力询问,完整的块询问块的和
#include <bits/stdc++.h>
using namespace std;
#define maxn 50010
#define maxt 240
int n,a[maxn],L[maxt],R[maxt],sum[maxt],pos[maxn],t;
bool flag[maxt];
inline 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_<<1)+(x_<<3)+c_-'0';c_=getchar();}
return x_*f_;
}
inline void solve_(int x) {
if(flag[x]) return ;
flag[x]=1;
for(int i=L[x];i<=R[x];++i) {
sum[x]-=a[i];
a[i]=sqrt(a[i]);
sum[x]+=a[i];
if(a[i]>1) flag[x]=0;
}
}
inline void update_(int l,int r,int w) {
int LL=pos[l],RR=pos[r];
if(LL==RR) {
for(int i=l;i<=r;++i) {
sum[LL]-=a[i];
a[i]=sqrt(a[i]);
sum[LL]+=a[i];
}
}
else {
for(int i=l;i<=R[LL];++i){
sum[LL]-=a[i];
a[i]=sqrt(a[i]);
sum[LL]+=a[i];
}
for(int i=L[RR];i<=r;++i) {
sum[RR]-=a[i];
a[i]=sqrt(a[i]);
sum[RR]+=a[i];
}
for(int i=LL+1;i<=RR-1;++i) solve_(i);
}
}
inline int query_(int l,int r) {
int LL=pos[l],RR=pos[r];
int ans=0;
if(LL==RR) {
for(int i=l;i<=r;++i) ans+=a[i];
}
else {
for(int i=l;i<=R[LL];++i) ans+=a[i];
for(int i=L[RR];i<=r;++i) ans+=a[i];
for(int i=LL+1;i<=RR-1;++i) ans+=sum[i];
}
return ans;
}
void readda_() {
n=read_();
for(int i=1;i<=n;++i) a[i]=read_();
t=sqrt(n);
for(int i=1;i<=t;++i) {
L[i]=(i-1)*t+1;
R[i]=i*t;
}
if(R[t]<n) {++t;L[t]=R[t-1]+1;R[t]=n;}
for(int i=1;i<=t;++i) {
for(int j=L[i];j<=R[i];++j) {
pos[j]=i;
sum[i]+=a[j];
}
}
for(int i=1;i<=n;++i) {
int pd=read_(),l=read_(),r=read_(),c=read_();
if(!pd) update_(l,r,c);
else printf("%d\n",query_(l,r));
}
}
int main() {
freopen("a.txt","r",stdin);
readda_();
return 0;
}