[知识学习] 分块
贴几个板子,供自己看
分块核心:暴力操作
把一串序列分成\(\sqrt{n}\)个块,对每个块操作
左端不完整块,右端不完整块暴力计算
中间完整块lazy标记
最简单的分块题:
#include <bits/stdc++.h>
#define N (100000+5)
using namespace std;
int n,k,a[N],lazy[N],pos[N],L[N],R[N];//a原数组,lazy懒标记,pos[i]:i对应的块,L[i]:块i的左端点,R[i]:块i的右端点
void add(int l,int r,int c){//区间加
for(int i=l;i<=min(R[pos[l]],r);i++){//左端不完整区间处理
a[i]+=c;
}
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++){//右端不完整区间处理
a[i]+=c;
}
}
for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c;//中间完整区间处理
}
int main(){
scanf("%d",&n);
k=sqrt(n);//每k个数分1块
int sum=n/k+(n%k!=0);//共分sum个块
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) pos[i]=(i-1)/k+1;//每个数在哪个块
for(int i=1;i<=sum;i++) L[i]=(i-1)*k+1,R[i]=i*k;//左右端点
R[sum]=n;
for(int i=1;i<=n;i++){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0) add(l,r,c);
else if(opt==1) printf("%d\n",a[r]+lazy[pos[r]]);
}
return 0;
}
支持区间加,查找区间内前驱
可以利用C++STL里的set维护每个块,查lower_bound
每次修改时先把块里的删掉,修改后在加进来
#include <bits/stdc++.h>
#define N (100000+5)
using namespace std;
int n,k,a[N],lazy[N],pos[N],L[N],R[N];
set<int> t[N];
void add(int l,int r,int c){//同上
for(int i=l;i<=min(R[pos[l]],r);i++){
t[pos[l]].erase(a[i]);//先删掉原始的
a[i]+=c;//更改
t[pos[l]].insert(a[i]);//再插入新的
}
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++){
t[pos[r]].erase(a[i]);
a[i]+=c;
t[pos[r]].insert(a[i]);
}
}
for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c;
}
int pre(int l,int r,int c){//查询前驱,用set维护
int ans=-1;
for(int i=l;i<=min(R[pos[l]],r);i++)//统计左端不完整区间
if(a[i]+lazy[pos[l]]<c) ans=max(ans,a[i]+lazy[pos[l]]);
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++)//统计右端不完整区间
if(a[i]+lazy[pos[r]]<c) ans=max(ans,a[i]+lazy[pos[r]]);
}
for(int i=pos[l]+1;i<pos[r];i++){//用STL找中间的最大值
int k=c-lazy[i];
set<int>::iterator it=t[i].lower_bound(k);
if(it==t[i].begin()) continue;
it--;
ans=max(ans,*it+lazy[i]);
}
return ans;
}
int main(){
scanf("%d",&n);
k=sqrt(n);
int sum=n/k+(n%k!=0);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) pos[i]=(i-1)/k+1,t[pos[i]].insert(a[i]);
for(int i=1;i<=sum;i++) L[i]=(i-1)*k+1,R[i]=i*k;
R[sum]=n;
for(int i=1;i<=n;i++){
int opt,l,r,c;
scanf("%d%d%d%d",&opt,&l,&r,&c);
if(opt==0) add(l,r,c);
else if(opt==1) printf("%d\n",pre(l,r,c));
}
return 0;
}
支持区间加,区间求和
思路大同小异,这里不细说,依然是老套路
#include <bits/stdc++.h>
#define N (100000+5)
#define int long long//开long long
using namespace std;
int n,k,b,a[N],pos[N],lazy[N],sum[N],L[N],R[N];
void add(int l,int r,int c){//区间加操作,不解释了
for(int i=l;i<=min(R[pos[l]],r);i++) a[i]+=c,sum[pos[l]]+=c;
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++) a[i]+=c,sum[pos[r]]+=c;
}
for(int i=pos[l]+1;i<pos[r];i++) lazy[i]+=c,sum[i]+=c*k;
}
int getsum(int l,int r,int c){//区间求和操作,一样的道理
int ans=0;
for(int i=l;i<=min(R[pos[l]],r);i++) ans+=a[i]+lazy[pos[l]],ans%=(c+1);
if(pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++) ans+=a[i]+lazy[pos[r]],ans%=(c+1);
}
for(int i=pos[l]+1;i<pos[r];i++) ans+=sum[i],ans%=(c+1);
return ans%(c+1);
}
signed main(){
scanf("%lld",&n);
k=sqrt(n);
b=(n/k)+(n%k!=0);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
pos[i]=(i-1)/k+1;
sum[pos[i]]+=a[i];
}
for(int i=1;i<=b;i++){
L[i]=(i-1)*k+1,R[i]=i*k;
}
R[b]=n;
for(int i=1;i<=n;i++){
int opt,l,r,c;
scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
if(opt==0) add(l,r,c);
else if(opt==1) printf("%lld\n",getsum(l,r,c));
}
return 0;
}
支持区间开方,区间求和
开方:最终得到的数都会是0或1(向下取整了)
所以分块维护,当哪个块里的数全部都是0或1是,再怎么开方都不会变了
所以用vis数组记录一下那些块已经只有0或1,就可以大大节约复杂度
#include <bits/stdc++.h>
#define N (1000000+5)
#define int long long
using namespace std;
int n,bl,k,a[N],sum[N],pos[N],L[N],R[N];
bool vis[N];
void solve(int x){//解决完整块开方的函数
if(vis[x]) return;//这个块全部都是0或1,直接跳出不用开方
vis[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) vis[x]=0;
}
}
void add(int l,int r,int c){//不完整块开方函数,同理
if(!vis[pos[l]]){
for(int i=l;i<=min(R[pos[l]],r);i++){
sum[pos[l]]-=a[i];
a[i]=sqrt(a[i]);
sum[pos[l]]+=a[i];
}
vis[pos[l]]=1;
for(int i=L[pos[l]];i<=R[pos[l]];i++){
if(a[i]>1){
vis[pos[l]]=0;
break;
}
}
}
if(!vis[pos[r]]&&pos[l]!=pos[r]){
for(int i=L[pos[r]];i<=r;i++){
sum[pos[r]]-=a[i];
a[i]=sqrt(a[i]);
sum[pos[r]]+=a[i];
}
vis[pos[r]]=1;
for(int i=L[pos[r]];i<=R[pos[r]];i++){
if(a[i]>1){
vis[pos[r]]=0;
break;
}
}
}
for(int i=pos[l]+1;i<pos[r];i++) solve(i);
}
int getsum(int l,int r,int c){
int ans=0;
for(int i=l;i<=min(R[pos[l]],r);i++) ans+=a[i];
if(pos[l]!=pos[r])for(int i=L[pos[r]];i<=r;i++) ans+=a[i];
for(int i=pos[l]+1;i<pos[r];i++) ans+=sum[i];
return ans;
}
signed main(){
scanf("%lld",&n);
k=sqrt(n),bl=(n/k)+(n%k!=0);//同样
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
pos[i]=(i-1)/k+1;
sum[pos[i]]+=a[i];
}
for(int i=1;i<=bl;i++){
L[i]=(i-1)*k+1;
R[i]=i*k;
}
R[bl]=n;
for(int i=1;i<=n;i++){
int opt,l,r,c;
scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
if(opt==0) add(l,r,c);
else if(opt==1) printf("%lld\n",getsum(l,r,c));
}
return 0;
}
就到这里吧
转载请注明出处--Xx_queue