浅谈分块
2023 8.3
差不多花了小半天的时间来打分块,感觉理解起来并不难,但调起来是真的烦,调完却有脱胎换骨的感觉
分块
分块算法的核心思想是把一整个大区间分为若干个小区间,在对某一个区间进行修改、查询时,把这一个区间内整块的整体处理,零散的单独处理,以达到优化时间的目的。由于分块算法还是有一部分需要暴力去跑,所以分块算法又被称为“优雅的暴力”。
简称大段维护,全局朴素。
分块
将序列分为若干个长度不超过 \(\sqrt[]{N}\) 的段。
记录每个段的左右端点,和每个值的所属块数。
区间操作
对于更改的 \((l,r)\) 朴素更改 \((l,r_l)\) 和 \((l_r,r)\) 的值。维护 \((l_r+1,r_l-1)\) 的值,用标记记录。
查询区间则像更改相同,还需要 前缀块和 数组。
单点查询则为 \(a_i+c_{bj_i}\) c 数组为标记,bj为所属块。
code
数列分块入门 1
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+10;
int n,a[N],t,l[N],r[N],bj[N],c[N];
inline void add(int x,int y,int z){
int p=bj[x],q=bj[y];
if(p==q){
for(int i=x;i<=y;++i)
a[i]+=z;
}
else{
for(int i=x;i<=r[p];++i) a[i]+=z;
for(int i=p+1;i<=q-1;++i) c[i]+=z;
for(int i=l[q];i<=y;++i) a[i]+=z;
}
return ;
}
signed main(void){
scanf("%lld",&n);
t=sqrt(n);
for(int i=1;i<=n;++i){
scanf("%lld",a+i);
}
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){
bj[j]=i;
}
}
for(int opt,x,y,z,i=1;i<=n;++i){
scanf("%lld%lld%lld%lld",&opt,&x,&y,&z);
if(opt==0){
add(x,y,z);
}
if(opt==1){//单点查询,原值+块值(块内每个点都+z)
printf("%lld\n",a[y]+c[bj[y]]);
}
}
return 0;
}
数列分块入门 2
此题稍一变型,即为 教主的魔法
只是变为多少大于等于的了,用 ans+=len[i]-lower_bound(qq[i].begin(),qq[i].end(),o-c[i])-qq[i].begin()
即可。
当然,还有一些读入问题。
教主的魔法 的代码戳这里。
#include<bits/stdc++.h>
using namespace std;
const int N=5e2+10;
int n,t,c[N],a[50010],l[N],r[N],bj[50010],len[N];
vector<int>qq[N];
inline int read(){
int x=0;bool f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=0;s=getchar();}
while(s>='0'&&s<='9'){x=(x<<1)+(x<<3)+(s^48);s=getchar();}
return f?x:-x;
}
inline void write(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
inline void uu(int x){
for(int i=0;i<=len[x];++i)
qq[x][i]=a[l[x]+i];
sort(qq[x].begin(),qq[x].end());
}
inline void add(int x,int y,int z){
int p=bj[x],q=bj[y];
if(p==q){
for(int i=x;i<=y;++i)
a[i]+=z;
uu(bj[x]);
}
else{
for(int i=x;i<=r[p];++i) a[i]+=z;
uu(bj[x]);
for(int i=p+1;i<=q-1;++i) c[i]+=z;
for(int i=l[q];i<=y;++i) a[i]+=z;
uu(bj[y]);
}
return ;
}
inline int ask(int x,int y,int z){
int ans=0,p=bj[x],q=bj[y],o=z*z;
if(p==q){
for(int i=x;i<=y;++i){
if(a[i]+c[p]<o){
++ans;
}
}
}
else{
for(int i=x;i<=r[p];++i){
if(a[i]+c[p]<o){
++ans;
}
}
for(int i=p+1;i<=q-1;++i){
ans+=lower_bound(qq[i].begin(),qq[i].end(),o-c[i])-qq[i].begin();
}
for(int i=l[q];i<=y;++i){
if(a[i]+c[q]<o){
++ans;
}
}
}
return ans;
}
signed main(void){
n=read();t=sqrt(n);//重点,分为根号n块 ,写成了log2(N)调了很久
for(int i=1;i<=n;++i){
a[i]=read();
}
for(int i=1;i<=t;++i){
l[i]=r[i-1]+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){
len[i]=r[i]-l[i]+1;
for(int j=l[i];j<=r[i];++j){
bj[j]=i;
qq[i].push_back(a[j]);
}
}
for(int i=1;i<=t;++i){
sort(qq[i].begin(),qq[i].end());
}
for(int opt,x,y,z,o,i=1;i<=n;++i){
opt=read();x=read();y=read();z=read();
if(opt==0){
add(x,y,z);
}
if(opt==1){
// printf("%d\n",ask(x,y,z));
write(ask(x,y,z));
putchar('\n');
}
}
return 0;
/*
4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2
*/
}
数列分块入门 3
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e2+10;
int n,t,c[N],a[100010],l[N],r[N],bj[100010],len[N];
vector<int>qq[N];
inline int read(){
int x=0;bool f=1;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=0;s=getchar();}
while(s>='0'&&s<='9'){x=(x<<1)+(x<<3)+(s^48);s=getchar();}
return f?x:-x;
}
inline void write(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
inline void uu(int x){
for(int i=0;i<=len[x];++i)
qq[x][i]=a[l[x]+i];
sort(qq[x].begin(),qq[x].end());
}
inline void add(int x,int y,int z){
int p=bj[x],q=bj[y];
if(p==q){
for(int i=x;i<=y;++i) a[i]+=z;
uu(bj[x]);
}
else{
for(int i=x;i<=r[p];++i) a[i]+=z;
uu(bj[x]);
for(int i=p+1;i<=q-1;++i) c[i]+=z;
for(int i=l[q];i<=y;++i) a[i]+=z;
uu(bj[y]);
}
return ;
}
inline int ask(int x,int y,int z){
int ans=-1,p=bj[x],q=bj[y],k;
if(p==q){
for(int i=x;i<=y;++i){
if(a[i]+c[p]<z){
ans=max(ans,a[i]+c[p]);
}
}
}
else{
for(int i=x;i<=r[p];++i){
if(a[i]+c[p]<z){
ans=max(ans,a[i]+c[p]);
}
}
for(int i=p+1;i<=q-1;++i){
k=lower_bound(qq[i].begin(),qq[i].end(),z-c[i])-qq[i].begin();//比它-标记值>=的第一个值
if(k) ans=max(ans,qq[i][k-1]+c[i]);
}
for(int i=l[q];i<=y;++i){
if(a[i]+c[q]<z){
ans=max(ans,a[i]+c[q]);
}
}
}
return ans;
}
signed main(void){
n=read();t=sqrt(n);
for(int i=1;i<=n;++i){
a[i]=read();
}
for(int i=1;i<=t;++i){
l[i]=r[i-1]+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){
len[i]=r[i]-l[i]+1;
for(int j=l[i];j<=r[i];++j){
bj[j]=i;
qq[i].push_back(a[j]);
}
}
for(int i=1;i<=t;++i){
sort(qq[i].begin(),qq[i].end());
}
for(int opt,x,y,z,i=1;i<=n;++i){
opt=read();x=read();y=read();z=read();
if(opt==0){
add(x,y,z);
}
if(opt==1){
// printf("%d\n",ask(x,y,z));
write(ask(x,y,z));
putchar('\n');
}
}
return 0;
/*
4
1 2 2 3
0 1 3 1
1 1 3 2
1 1 4 1
1 2 3 2
*/
}
数列分块入门 4
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+10;
int n,a[N],t,l[N],r[N],bj[N],c[N],sum[N];
inline void init(){
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){
bj[j]=i;
sum[i]+=a[j];
}
}
}
inline void add(int x,int y,int z){
int p=bj[x],q=bj[y];
if(p==q){
for(int i=x;i<=y;++i)
a[i]+=z;
sum[p]+=z*(y-x+1);
}
else{
for(int i=x;i<=r[p];++i) a[i]+=z;
sum[p]+=z*(r[p]-x+1);
for(int i=p+1;i<=q-1;++i) c[i]+=z;
for(int i=l[q];i<=y;++i) a[i]+=z;
sum[q]+=z*(y-l[q]+1);
}
return ;
}
inline int ask(int x,int y){
int p=bj[x],q=bj[y],ans=0;
if(p==q){
for(int i=x;i<=y;++i) ans+=a[i];
ans+=c[p]*(y-x+1);
}
else{
for(int i=x;i<=r[p];++i) ans+=a[i];
ans+=c[p]*(r[p]-x+1);
for(int i=p+1;i<=q-1;++i) ans+=sum[i]+c[i]*(r[i]-l[i]+1);
for(int i=l[q];i<=y;++i) ans+=a[i];
ans+=c[q]*(y-l[q]+1);
}
return ans;
}
signed main(void){
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",a+i);
init();
for(int opt,x,y,z,i=1;i<=n;++i){
scanf("%lld%lld%lld%lld",&opt,&x,&y,&z);
if(opt==0){
add(x,y,z);
}
if(opt==1){
printf("%lld\n",(ask(x,y)%(z+1)));
}
}
return 0;
}