题目链接
https://loj.ac/problems/search?keyword=分块
参考博客:http://hzwer.com/8053.html
1.区间加法,单点查值
完整的块打个tag标记,非完整直接暴力修改
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e4 + 10;
const ll mod = 1000000007;
const int base=131;
int n,block;
int tag[50005],bl[50005],v[50005];
void add(int l,int r,int c)
{
for(int i=l;i<=min(bl[l]*block,r);i++) v[i]+=c;
if(bl[l]!=bl[r])
for(int i=(bl[r]-1)*block+1;i<=r;i++) v[i]+=c;
for(int i=bl[l]+1;i<=bl[r]-1;i++)
tag[i]+=c;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
block=sqrt(n);
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1;i<=n;i++) bl[i]=(i-1)/block+1;
for(int i=1;i<=n;i++)
{
int f ,l ,r, c;
cin>>f>>l>>r>>c;
if(f==0) add(l,r,c);
else
cout<<v[r]+tag[bl[r]]<<endl;
}
}
2.区间加法,询问区间内小于某个值x的元素个数
每个区间vector维护有序序列,完整区间直接二分查询,未完整暴力查询
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e4 + 10;
const ll mod = 1000000007;
const int base=131;
int n,block;
int tag[50005],bl[50005],a[50005];
vector<int> v[50006];
void reset(int x)
{
v[x].clear();
for(int i=(x-1)*block+1;i<=min(x*block,n);i++)
{
v[x].push_back(a[i]);
}
sort(v[x].begin(),v[x].end());
}
void add(int l,int r,int c)
{
for(int i=l;i<=min(bl[l]*block,r);i++) a[i]+=c;
reset(bl[l]);
if(bl[l]!=bl[r])
{
for(int i=(bl[r]-1)*block+1;i<=r;i++) a[i]+=c;
reset(bl[r]);
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
tag[i]+=c;
}
int query(int l,int r,int c)
{
int ans=0;
for(int i=l;i<=min(bl[l]*block,r);i++)
{
if(a[i]+tag[bl[l]]<c) ans++;
}
if(bl[l]!=bl[r])
{
for(int i=(bl[r]-1)*block+1;i<=r;i++)
if(a[i]+tag[bl[r]]<c) ans++;
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
{
int x=c-tag[i];
ans+=lower_bound(v[i].begin(),v[i].end(),x)-v[i].begin();
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
block=sqrt(n);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
bl[i]=(i-1)/block+1;
v[bl[i]].push_back(a[i]);
}
for(int i=1;i<=bl[n];i++)
sort(v[i].begin(),v[i].end());
for(int i=1;i<=n;i++)
{
int f ,l ,r, c;
cin>>f>>l>>r>>c;
if(f==0) add(l,r,c);
else
cout<<query(l,r,c*c)<<endl;
}
}
3.区间加法,询问区间内小于某个值 x的前驱(比其小的最大元素)。
同理每个块维护有序区间,完整二分,两边暴力
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e5 + 10;
const ll mod = 1000000007;
const int base=131;
int n,block;
int tag[N],bl[N],a[N];
vector<int> v[N];
void reset(int x)
{
v[x].clear();
for(int i=(x-1)*block+1;i<=min(x*block,n);i++)
{
v[x].push_back(a[i]);
}
sort(v[x].begin(),v[x].end());
}
void add(int l,int r,int c)
{
for(int i=l;i<=min(bl[l]*block,r);i++) a[i]+=c;
reset(bl[l]);
if(bl[l]!=bl[r])
{
for(int i=(bl[r]-1)*block+1;i<=r;i++) a[i]+=c;
reset(bl[r]);
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
tag[i]+=c;
}
int query(int l,int r,int c)
{
int ans=-1;
for(int i=l;i<=min(bl[l]*block,r);i++)
{
if(a[i]+tag[bl[l]]<c) ans=max(ans,a[i]+tag[bl[l]]);
}
if(bl[l]!=bl[r])
{
for(int i=(bl[r]-1)*block+1;i<=r;i++)
if(a[i]+tag[bl[r]]<c)
ans=max(ans,a[i]+tag[bl[r]]);
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
{
int x=c-tag[i];
auto it=lower_bound(v[i].begin(),v[i].end(),x);
if(it==v[i].begin()) continue;
--it;
ans=max(ans,*it+tag[i]);
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
block=sqrt(n);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
bl[i]=(i-1)/block+1;
v[bl[i]].push_back(a[i]);
}
for(int i=1;i<=bl[n];i++)
sort(v[i].begin(),v[i].end());
for(int i=1;i<=n;i++)
{
int f ,l ,r, c;
cin>>f>>l>>r>>c;
if(f==0) add(l,r,c);
else
cout<<query(l,r,c)<<endl;
}
}
4.区间加法,区间求和
维护每个块的和,完整时直接+block*tag,非完整暴力修改
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e4 + 10;
const ll mod = 1000000007;
const int base=131;
ll n,block;
ll tag[50005],bl[50005],v[50005],num[50005];
void add(ll l,ll r,ll c)
{
for(int i=l;i<=min(bl[l]*block,r);i++) v[i]+=c,num[bl[i]]+=c;
if(bl[l]!=bl[r])
for(int i=(bl[r]-1)*block+1;i<=r;i++) v[i]+=c,num[bl[i]]+=c;
for(int i=bl[l]+1;i<=bl[r]-1;i++)
tag[i]+=c;
}
ll query(ll l,ll r,ll c)
{
ll ans=0;
for(int i=l;i<=min(bl[l]*block,r);i++) ans=(ans+v[i]+tag[bl[i]])%(c+1);
if(bl[l]!=bl[r])
for(int i=(bl[r]-1)*block+1;i<=r;i++) ans=(ans+v[i]+tag[bl[i]])%(c+1);
for(int i=bl[l]+1;i<=bl[r]-1;i++)
ans=(ans+block*tag[i]+num[i])%(c+1);
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
block=sqrt(n);
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1;i<=n;i++) bl[i]=(i-1)/block+1,num[bl[i]]=num[bl[i]]+v[i];
//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
for(int i=1;i<=n;i++)
{
ll f ,l ,r, c;
cin>>f>>l>>r>>c;
if(f==0) add(l,r,c);
else
cout<<query(l,r,c)<<endl;
//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
}
}
5.区间开方,区间求和
开方操作会在有限次变为0,所以我们只需要用一个tag标记每一块是否为0,如果不为0,暴力修改每一块,并且维护每一块的和,如果为0就直接返回
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e4 + 10;
const ll mod = 1000000007;
const int base=131;
ll n,block;
ll tag[50005],bl[50005],v[50005],num[50005],flag[50005];
void kai(ll x)
{
if(flag[x]) return ;
flag[x]=1;
num[x]=0;
for(int i=(x-1)*block+1;i<=x*block;i++)
{
v[i]=sqrt(v[i]),num[x]+=v[i];
if(v[i]>1) flag[x]=0;
}
}
void add(ll l,ll r,ll c)
{
for(int i=l;i<=min(bl[l]*block,r);i++)
{
num[bl[i]]-=v[i];
v[i]=sqrt(v[i]);
num[bl[i]]+=v[i];
}
if(bl[l]!=bl[r])
for(int i=(bl[r]-1)*block+1;i<=r;i++)
{
num[bl[i]]-=v[i];
v[i]=sqrt(v[i]);
num[bl[i]]+=v[i];
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
kai(i);
}
ll query(ll l,ll r)
{
ll ans=0;
for(int i=l;i<=min(bl[l]*block,r);i++) ans+=v[i];
if(bl[l]!=bl[r])
for(int i=(bl[r]-1)*block+1;i<=r;i++) ans+=v[i];
for(int i=bl[l]+1;i<=bl[r]-1;i++)
ans=ans+num[i];
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
block=sqrt(n);
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1;i<=n;i++) bl[i]=(i-1)/block+1,num[bl[i]]=num[bl[i]]+v[i];
//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
for(int i=1;i<=n;i++)
{
ll f ,l ,r, c;
cin>>f>>l>>r>>c;
if(f==0) add(l,r,c);
else
cout<<query(l,r)<<endl;
//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
}
}
6.单点插入,单点询问,数据随机生成
关于插入操作,如果插入过多可能会使块变得很大,影响块的查询时间复杂度,所以我们考虑超过一定数量,直接重构整个块
我们考虑每
n
\sqrt{n}
n次插入后,重构整个块,重构的时间复杂度是n,那么总共也就是
n
n
n\sqrt{n}
nn的时间,这个时间复杂度是可接受的
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e6 + 10;
const ll mod = 1000000007;
const int base=131;
ll n,block,m;
ll tag[N],bl[N],tmp[N],a[N];
vector<int> v[N];
P query(int x)
{
int k=1;
while(x>v[k].size())
x-=v[k].size(),k++;
return make_pair(k,x-1);
}
void rebuild()
{
int cnt=0;
for(int i=1;i<=m;i++)
{
for(int x:v[i]) tmp[++cnt]=x;
v[i].clear();
}
int blo2=sqrt(cnt);
for(int i=1;i<=cnt;i++)
{
v[(i-1)/blo2+1].push_back(tmp[i]);
}
m=(n-1)/blo2+1;
}
void add(ll l,ll r)
{
P now=query(l);
v[now.fi].insert(v[now.fi].begin()+now.se,r);
if(v[now.fi].size()>=20*block)
rebuild();
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
block=sqrt(n);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) v[(i-1)/block+1].push_back(a[i]);
m=(n-1)/block+1;
//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
for(int i=1;i<=n;i++)
{
ll f ,l ,r, c;
cin>>f>>l>>r>>c;
if(f==0) add(l,r);
else
{
P t=query(r);
cout<<v[t.fi][t.se]<<endl;
}
//for(int i=1;i<=bl[n];i++) cout<<num[i]<<" ";cout<<endl;
}
}
7.区间乘法,区间加法,单点询问
考虑维护两个标记,维护一个乘法标记,一个加法标记
让乘法标记优先级高于加法标记
具体意思就是乘法标记能同时让乘法和加法标记翻倍
加法标记直接加在加法标记上
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e6 + 10;
const ll mod = 10007;
const int base=131;
ll n,block,m;
ll tag[N],bl[N],tagmul[N],a[N];
vector<int> v[N];
void reset(int x)
{
for(int i=(x-1)*block+1;i<=min(x*block,n);i++)
{
a[i]=(a[i]*tagmul[x]+tag[x])%mod;
}
tagmul[x]=1,tag[x]=0;
}
void add(ll l,ll r,ll c)
{
reset(bl[l]);
for(int i=l;i<=min(bl[l]*block,r);i++)
{
a[i]=(a[i]+c)%mod;
}
if(bl[l]!=bl[r])
{
reset(bl[r]);
for(int i=(bl[r]-1)*block+1;i<=r;i++)
a[i]=(a[i]+c)%mod;
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
tag[i]=(tag[i]+c)%mod;
}
void mul(ll l,ll r,ll c)
{
reset(bl[l]);
for(int i=l;i<=min(bl[l]*block,r);i++)
a[i]=(a[i]*c)%mod;
if(bl[l]!=bl[r])
{
reset(bl[r]);
for(int i=(bl[r]-1)*block+1;i<=r;i++)
a[i]=(a[i]*c)%mod;
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
tag[i]=(tag[i]*c)%mod,tagmul[i]=(tagmul[i]*c)%mod;
}
ll query(ll x)
{
return (a[x]*tagmul[bl[x]]+tag[bl[x]])%mod;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
block=sqrt(n);
for(int i=1;i<=n;i++) cin>>a[i],a[i]%=10007;
for(int i=1;i<=n;i++)
{
tagmul[i]=1;
bl[i]=(i-1)/block+1;
}
for(int i=1;i<=n;i++)
{
int opt,l,r,c;
cin>>opt>>l>>r>>c;
if(!opt)
{
add(l,r,c);
}
else if(opt&1)
{
mul(l,r,c);
}
else cout<<query(r)%mod<<endl;
//for(int j=1;j<=n;j++) cout<<query(j)<<" ";cout<<endl;
}
}
8.操作涉及区间询问等于一个数 的元素,并将这个区间的所有元素改为 。
询问后一整段都会被修改,几次询问后数列可能只剩下几段不同的区间了
维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就O(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。
假设初始序列都是同一个值,那么查询是O(√n),如果这时进行一个区间操作,它最多破坏首尾2个块的标记,所以只能使后面的询问至多多2个块的暴力时间,所以均摊每次操作复杂度还是O(√n)。
换句话说,要想让一个操作耗费O(n)的时间,要先花费√n个操作对数列进行修改。
#include<bits/stdc++.h>
#define fi first
#define se second
#define FOR(a) for(int i=0;i<a;i++)
#define sc(a) scanf("%d",&a)
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e6 + 10;
const ll mod = 10007;
const int base=131;
ll n,block,m;
ll tag[N],bl[N],tagmul[N],a[N];
vector<int> v[N];
void reset(int x)
{
if(tag[x]==-1) return ;
for(int i=(x-1)*block+1;i<=min(n,x*block);i++)
{
a[i]=tag[x];
}
tag[x]=-1;
}
ll kk(int x,int c)
{
int ans=0;
for(int i=(x-1)*block+1;i<=min(n,x*block);i++)
{
if(a[i]==c)
{
ans++;
}
else a[i]=c;
}
tag[x]=c;
return ans;
}
ll query(ll l,ll r,ll c)
{
ll ans=0;
reset(bl[l]);
for(int i=l;i<=min(bl[l]*block,r);i++)
{
if(a[i]==c) ans++;
else a[i]=c;
}
if(bl[l]!=bl[r])
{
reset(bl[r]);
for(int i=(bl[r]-1)*block+1;i<=r;i++)
{
if(a[i]==c) ans++;
else a[i]=c;
}
}
for(int i=bl[l]+1;i<=bl[r]-1;i++)
{
if(tag[i]==-1)
{
ans+=kk(i,c);
}
else if(tag[i]==c) ans+=block;
else tag[i]=c;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;
block=sqrt(n);
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
bl[i]=(i-1)/block+1;
}
for(int i=1;i<=bl[n];i++)
{
int st=(bl[i]-1)*block+1;
tag[i]=a[st];
for(int j=(bl[i]-1)*block+1;j<=min(bl[i]*block,n);j++)
{
if(a[j]!=a[st])
{
tag[i]=-1;
break;
}
}
}
for(int i=1;i<=n;i++)
{
int opt,l,r,c;
cin>>l>>r>>c;
cout<<query(l,r,c)<<endl;
//update(l,r,c);
//for(int j=1;j<=n;j++) cout<<query(j)<<" ";cout<<endl;
}
}
9.询问区间的最小众数
这道题目是强制在线的,考虑分块情况,众数只可能出现在完整块中,或者左右两边的数中,所以我们用
n
n
n\sqrt{n}
nn预处每个块的最小众数,并且离散化后将每个数的下标存入对应的vector中,这样我们查询数量时,可以直接二分该树的vector下标范围查询数量了,对于这道题块大小为50跑得最快,我也不知道为什么…
#include<bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef pair<int, int> P;
typedef pair<P, int> LP;
const ll inf = 1e17 + 10;
const int N = 1e6 + 10;
const ll mod = 10007;
const int base=131;
int n,block,m,id;
int tag[N],bl[N],val[N],a[N];
int cal[N],num[N];
vector<int> v[N];
unordered_map<int,int> mp;
int f[2005][2005];
void pre(int x)
{
int k=0;
for(int i=1;i<=n;i++) num[i]=0;
for(int i=(x-1)*block+1;i<=n;i++)
{
num[a[i]]++;
if(num[a[i]]>num[k]||(num[k]==num[a[i]]&&val[a[i]]<val[k])) k=a[i];
f[x][bl[i]]=k;
}
}
int qnum(int l,int r,int x)
{
int t=upper_bound(v[x].begin(),v[x].end(),r)-lower_bound(v[x].begin(),v[x].end(),l);
return t;
}
int query(int l,int r)
{
int ans,mx;
ans=f[bl[l]+1][bl[r]-1];
mx=qnum(l,r,ans);
for(int i=l;i<=min(bl[l]*block,r);i++)
{
int x=qnum(l,r,a[i]);
if(x>mx||(x==mx&&val[a[i]]<val[ans])) ans=a[i],mx=x;
}
if(bl[l]!=bl[r])
{
for(int i=(bl[r]-1)*block+1;i<=r;i++)
{
int x=qnum(l,r,a[i]);
if(x>mx||(x==mx&&val[a[i]]<val[ans])) ans=a[i],mx=x;
}
}
return val[ans];
}
int main()
{
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
scanf("%d",&n);
//cout<<log2(n)<<endl;
block=50;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
bl[i]=(i-1)/block+1;
if(!mp[a[i]]) mp[a[i]]=++id,val[id]=a[i];
a[i]=mp[a[i]];
v[a[i]].push_back(i);
}
for(int i=1;i<=bl[n];i++)
{
pre(i);
}
for(int i=1;i<=n;i++)
{
int l,r,c;
scanf("%d %d",&l,&r);
printf("%d\n",query(l,r));
//update(l,r,c);
//for(int j=1;j<=n;j++) cout<<query(j)<<" ";cout<<endl;
}
}