分块
分块
定义:一种暴力数据结构,即将数列分为 \(w\) 块,以块为单位维护一些信息。
P3372
如何使用分块实现线段树?
考虑区间修改:散块暴力修改,整块打标记(维护 \(tag_i\))即可。
考虑区间查询:散块暴力求和,整块整体求和(维护 \(sum_i\))即可。
这是个很简单的题目,却透露出一个通用的分块思路:散块暴力、整块维护。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,m;
int a[N],sum[N],tag[N],L[N],R[N],pos[N];
void upd(int l,int r,int k){
int x=pos[l],y=pos[r];
if(x==y){
for(int i=l;i<=r;i++)
a[i]+=k,sum[x]+=k;
}
else{
for(int i=l;i<=R[x];i++)
a[i]+=k,sum[x]+=k;
for(int i=x+1;i<y;i++)
tag[i]+=k;
for(int i=L[y];i<=r;i++)
a[i]+=k,sum[y]+=k;
}
}
int qry(int l,int r){
int x=pos[l],y=pos[r],res=0;
if(x==y){
for(int i=l;i<=r;i++)
res+=a[i]+tag[x];
}
else{
for(int i=l;i<=R[x];i++)
res+=a[i]+tag[x];
for(int i=x+1;i<y;i++)
res+=sum[i]+(R[i]-L[i]+1)*tag[i];
for(int i=L[y];i<=r;i++)
res+=a[i]+tag[y];
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
int t=sqrt(n);
for(int i=1;i<=t;i++){
L[i]=(i-1)*sqrt(n)+1;
R[i]=i*sqrt(n);
}
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++)
sum[i]+=a[j],pos[j]=i;
while(m--){
int op,x,y,k;
cin>>op>>x>>y;
if(op==1){
cin>>k;
upd(x,y,k);
}
else
cout<<qry(x,y)<<'\n';
}
return 0;
}
P2801 & P5356
对于这道题,显然我们容易想到对于每个块维护一个有序数组,这样就可以用二分的方式回答询问了。
考虑区间修改:同上,注意散块修改后需要重新排序。
考虑区间查询:散块暴力是简单的,整块查询因为是整体加 \(tag_i\),有序性不变,仅需进行二分即可。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+5;
int n,q;
int a[N],sum[N],tag[N],L[N],R[N],pos[N];
vector<int> g[N];
void resort(int x){
g[x].clear();
for(int i=L[x];i<=R[x];i++)
g[x].push_back(a[i]);
sort(g[x].begin(),g[x].end());
}
int fnd(int a,int x){
int l=-1,r=g[a].size();
while(l+1<r){
int mid=(l+r)>>1;
if(g[a][mid]>=x)
r=mid;
else
l=mid;
}
return g[a].size()-r;
}
void upd(int l,int r,int k){
int x=pos[l],y=pos[r];
if(x==y){
for(int i=l;i<=r;i++)
a[i]+=k;
resort(x);
}
else{
for(int i=l;i<=R[x];i++)
a[i]+=k;
resort(x);
for(int i=x+1;i<y;i++)
tag[i]+=k;
for(int i=L[y];i<=r;i++)
a[i]+=k;
resort(y);
}
}
int qry(int l,int r,int c){
int x=pos[l],y=pos[r],res=0;
if(x==y){
for(int i=l;i<=r;i++)
res+=(a[i]+tag[x]>=c);
}
else{
for(int i=l;i<=R[x];i++)
res+=(a[i]+tag[x]>=c);
for(int i=x+1;i<y;i++)
res+=fnd(i,c-tag[i]);
for(int i=L[y];i<=r;i++)
res+=(a[i]+tag[y]>=c);
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>q;
for(int i=1;i<=n;i++)
cin>>a[i];
int t=sqrt(n);
for(int i=1;i<=t;i++){
L[i]=(i-1)*sqrt(n)+1;
R[i]=i*sqrt(n);
}
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++)
g[i].push_back(a[j]),pos[j]=i;
sort(g[i].begin(),g[i].end());
}
while(q--){
char op;
int x,y,k;
cin>>op>>x>>y>>k;
if(op=='M')
upd(x,y,k);
else
cout<<qry(x,y,k)<<'\n';
}
return 0;
}
至于 P5356,需要外边还套一个值域二分,思路很简单但是代码很大且需要卡常,反正我是卡不动了,30分记录。
总结:查排名对应的数 / 数对应的排名,考虑排序。
P4145
诈骗题,看上去十分唬人,实际上不难发现开方开了 \(5\) 次以上就会都变成 \(1\),这以后就无需修改了,于是暴力修改即可,代码很好写。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,m;
int a[N],pos[N],L[N],R[N],sum[N],cs[N];
void upd(int l,int r){
int x=pos[l],y=pos[r];
if(x==y){
if(cs[x]<=5)
for(int i=l;i<=r;i++)
sum[x]-=a[i],a[i]=sqrt(a[i]),sum[x]+=a[i];
}
else{
if(cs[x]<=5)
for(int i=l;i<=R[x];i++)
sum[x]-=a[i],a[i]=sqrt(a[i]),sum[x]+=a[i];
for(int i=x+1;i<y;i++){
if(cs[i]<=5){
for(int j=L[i];j<=R[i];j++){
sum[i]-=a[j];
a[j]=sqrt(a[j]);
sum[i]+=a[j];
}
cs[i]++;
}
}
if(cs[y]<=5)
for(int i=L[y];i<=r;i++)
sum[y]-=a[i],a[i]=sqrt(a[i]),sum[y]+=a[i];
}
}
int qry(int l,int r){
int x=pos[l],y=pos[r],res=0;
if(x==y)
for(int i=l;i<=r;i++)
res+=a[i];
else{
for(int i=l;i<=R[x];i++)
res+=a[i];
for(int i=x+1;i<y;i++)
res+=sum[i];
for(int i=L[y];i<=r;i++)
res+=a[i];
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
int t=sqrt(n);
for(int i=1;i<=t;i++){
L[i]=(i-1)*sqrt(n)+1;
R[i]=i*sqrt(n);
}
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];
cin>>m;
while(m--){
int k,l,r;
cin>>k>>l>>r;
if(l>r)
swap(l,r);
if(!k)
upd(l,r);
else
cout<<qry(l,r)<<'\n';
}
return 0;
}
总结:先发掘性质再做题。
P3203
先考虑暴力,如果没有修改操作,直接倒序做一遍 dp 即可回答询问;这里有修改操作,于是每次修改后必须重新做一遍 dp,太 man 了,考虑用一个分块优化之。
以块为单位考虑,以前我们维护的是每个点的后继是哪里和跳出界要多少步,现在我们就应当维护每个点跳出块以后到了哪里和跳出块要多少步。
对于询问,一个一个块地跳即可;对于修改,块内暴力修改即可。
这是一个极具启发性的好题,它表明分块的意义即在于缩小考虑范围(从全局缩小到每个块),处理好每个块的信息再进行合并。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+5;
int n,m;
int a[N],pos[N],L[N],R[N],step[N],to[N];
void upd(int x,int k){
a[x]=k;
for(int i=R[pos[x]];i>=L[pos[x]];i--){
to[i]=i+a[i];
if(to[i]>R[pos[x]])
step[i]=1;
else
step[i]=step[to[i]]+1,to[i]=to[to[i]];
}
}
int qry(int x){
int res=0;
while(x<=n)
res+=step[x],x=to[x];
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
int t=sqrt(n);
for(int i=1;i<=t;i++){
L[i]=(i-1)*sqrt(n)+1;
R[i]=i*sqrt(n);
}
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=n;i>=1;i--){
to[i]=i+a[i];
if(to[i]>R[pos[i]])
step[i]=1;
else
step[i]=step[to[i]]+1,to[i]=to[to[i]];
}
cin>>m;
while(m--){
int op,x,k;
cin>>op>>x;
x++;
if(op==2)
cin>>k,upd(x,k);
else
cout<<qry(x)<<'\n';
}
return 0;
}
P1975
这个题,我们考虑处理增量。
考虑 \(x,y\) 交换以后,会产生三部分增量:
-
\([x+1,y-1]\) 中,原来比 \(x\) 大的和比 \(y\) 小的,会贡献逆序对;
-
反之,比 \(x\) 小的和比 \(y\) 大的,会扣除逆序对;
-
最后,若 \(x<y\) 则增加一个逆序对,\(x>y\) 扣除一个。
然后我们发现这转化为了一个求比某个数大 / 小的元素有多少个,可以使用分块解决(类似“教主的魔法”)。
code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#include <cassert>
#define int long long
using namespace std;
const int N=1e6+5;
int n,m,a[N];
int tree[N];
struct H{
int val,id;
}h[N];
int L[N],R[N],pos[N];
vector<int> g[N];
int lowbit(int x){
return x&(-x);
}
void upd(int x,int y){
for(;x<=n;x+=lowbit(x)) tree[x]+=y;
}
int qry(int x){
int s=0;
for(;x;x-=lowbit(x)) s+=tree[x];
return s;
}
bool cmp(H x,H y){
if(x.val==y.val)
return x.id>y.id;
return x.val>y.val;
}
void resort(int x){
g[x].clear();
for(int i=L[x];i<=R[x];i++)
g[x].push_back(a[i]);
sort(g[x].begin(),g[x].end());
}
int fndup(int a,int x){
int l=-1,r=g[a].size();
while(l+1<r){
int mid=(l+r)>>1;
if(g[a][mid]>x)
r=mid;
else
l=mid;
}
return g[a].size()-r;
}
int fnddown(int a,int x){
int l=-1,r=g[a].size();
while(l+1<r){
int mid=(l+r)>>1;
if(g[a][mid]<x)
l=mid;
else
r=mid;
}
return l+1;
}
int qryup(int l,int r,int c){
if(l>r)
return 0;
int x=pos[l],y=pos[r],res=0;
if(x==y){
for(int i=l;i<=r;i++)
res+=(a[i]>c);
}
else{
for(int i=l;i<=R[x];i++)
res+=(a[i]>c);
for(int i=x+1;i<y;i++)
res+=fndup(i,c);
for(int i=L[y];i<=r;i++)
res+=(a[i]>c);
}
return res;
}
int qrydown(int l,int r,int c){
if(l>r)
return 0;
//assert(l >= 1 && l <= n);
//assert(r >= 1 && r <= n);
//assert(l <= r);
int x=pos[l],y=pos[r],res=0;
if(x==y){
for(int i=l;i<=r;i++)
res+=(a[i]<c);
}
else{
for(int i=l;i<=R[x];i++)
res+=(a[i]<c);
for(int i=x+1;i<y;i++)
res+=fnddown(i,c);
for(int i=L[y];i<=r;i++)
res+=(a[i]<c);
}
return res;
}
signed main(){
//freopen("1975.txt","r",stdin);
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n;
//assert(n >= 1 && n <= 20000);
for(int i=1;i<=n;i++)
cin>>a[i],h[i].val=a[i],h[i].id=i;
sort(h+1,h+n+1,cmp);
int ans=0;
for(int i=1;i<=n;i++){
ans+=qry(h[i].id-1);
upd(h[i].id,1);
}
cout<<ans<<'\n';
int t=sqrt(n);
for(int i=1;i<=t;i++){
L[i]=(i-1)*sqrt(n)+1;
R[i]=i*sqrt(n);
//assert(L[i] <= R[i]);
}
if(R[t]<n){
t++;
L[t]=R[t-1]+1;
R[t]=n;
//assert(L[t] <= R[t]);
}
for(int i=1;i<=t;i++){
for(int j=L[i];j<=R[i];j++)
g[i].push_back(a[j]),pos[j]=i;
sort(g[i].begin(),g[i].end());
}
cin>>m;
//assert(m >= 1 && m <= 2000);
while(m--){
int x,y;
cin>>x>>y;
if(x>y)
swap(x,y);
//assert(x >= 1 && x <= n);
//assert(y >= 1 && y <= n);
//assert(x != y);
int xx=a[x],yy=a[y];
swap(a[x],a[y]);
resort(pos[x]),resort(pos[y]);
//assert(is_sorted(g[pos[x]].begin(), g[pos[x]].end()));
//assert(is_sorted(g[pos[y]].begin(), g[pos[y]].end()));
ans-=qrydown(x+1,y-1,xx);
ans+=qryup(x+1,y-1,xx);
ans-=qryup(x+1,y-1,yy);
ans+=qrydown(x+1,y-1,yy);
if(yy>xx)
ans++;
else if(yy<xx)
ans--;
//assert(ans >= 0);
cout<<ans<<'\n';
}
return 0;
}
总结:
-
处理增量思想。
-
显式处理非法情况、调试用
assert(血的教训)。
P3710
逆天分块。
若没有撤销操作,直接线段树维护即可。
考虑到有这个撤销操作,一种暴力的方式就是重新做一遍,显然这太 man 了。
我们考虑缩小这个暴力的范围,即对于操作序列进行分块,每次对于被撤销的操作的块重新做一遍,这样撤销的时间复杂度可以降至单次 \(O(\sqrt{m} \log n)\)。
同时,每一个块需要一棵线段树,维护 \(a,b\) 两个值,表示 \(x\) 经过这个块之后能变为 \(ax+b\)。于是对于查询操作,直接从前缀的块累加过来即可。\(O(\sqrt{m} \log n)\)。
时间复杂度降下来了,但是空间复杂度飙升至 \(O(n \sqrt{m})\),炸了。
两个方法:
-
将 \(l,r\) 离散化。
-
调块长。
一些细节:
-
撤销后的操作使用并查集跳过。
-
取模操作尽量用减法代替。
-
线段树开大空间。
然后理论上就可以过了。
code(233 行,4KB)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define sil static inline
using namespace std;
inline int read(){ int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; }
void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; }
const int N=150001,M=1e3+5;
const long long MOD=998244353;
int n,m,all,siz,t;
int L[M],R[M],pos[N];
int g[M][M*4+5],tot[M];
int fa[N],root[N];
struct Q{
int op,l,r,d,p;
}q[N];
struct SGT{
int lt,rt;
long long a,b,addtag,multag;
bool flag;
}tr[N*20];
sil int ask(int x,int a){
return lower_bound(g[a]+1,g[a]+tot[a]+1,x)-g[a];
}
sil int fnd(int x){
return (fa[x]==x?x:fa[x]=fnd(fa[x]));
}
sil void make(int id,int addtag,int multag,int flag){
if(flag){
tr[id].a=tr[id].multag=1;
tr[id].b=tr[id].addtag=0;
tr[id].flag=1;
}
if(multag!=1){
tr[id].a=(tr[id].a*multag%MOD)%MOD;
tr[id].multag=(tr[id].multag*multag%MOD)%MOD;
tr[id].b=(tr[id].b*multag%MOD)%MOD;
tr[id].addtag=(tr[id].addtag*multag%MOD)%MOD;
}
if(addtag!=0){
tr[id].b=tr[id].b+addtag;
tr[id].b=(tr[id].b>MOD?tr[id].b-MOD:tr[id].b);
tr[id].addtag=tr[id].addtag+addtag;
tr[id].addtag=(tr[id].addtag>MOD?tr[id].addtag-MOD:tr[id].addtag);
}
}
sil int build(int lt,int rt){
int id=++all;
tr[id].a=tr[id].multag=1;
if(lt==rt)
return id;
int mid=(lt+rt)>>1;
tr[id].lt=build(lt,mid);
tr[id].rt=build(mid+1,rt);
return id;
}
sil void upd_add(int p,int ql,int qr,int val,int lt,int rt){
if(ql<=lt&&rt<=qr){
make(p,val,1,0);
return;
}
make(tr[p].lt,tr[p].addtag,tr[p].multag,tr[p].flag);
make(tr[p].rt,tr[p].addtag,tr[p].multag,tr[p].flag);
tr[p].addtag=0,tr[p].multag=1,tr[p].flag=0;
int mid=(lt+rt)>>1;
if(ql<=mid)
upd_add(tr[p].lt,ql,qr,val,lt,mid);
if(qr>mid)
upd_add(tr[p].rt,ql,qr,val,mid+1,rt);
}
sil void upd_mul(int p,int ql,int qr,int val,int lt,int rt){
if(ql<=lt&&rt<=qr){
make(p,0,val,0);
return;
}
make(tr[p].lt,tr[p].addtag,tr[p].multag,tr[p].flag);
make(tr[p].rt,tr[p].addtag,tr[p].multag,tr[p].flag);
tr[p].addtag=0,tr[p].multag=1,tr[p].flag=0;
int mid=(lt+rt)>>1;
if(ql<=mid)
upd_mul(tr[p].lt,ql,qr,val,lt,mid);
if(qr>mid)
upd_mul(tr[p].rt,ql,qr,val,mid+1,rt);
}
sil void qry(int p,int pos,int lt,int rt,long long& val){
if(lt==rt){
val=tr[p].a%MOD*1ll*val+tr[p].b;
val=(val>MOD?val-MOD:val);
return;
}
int mid=(lt+rt)>>1;
make(tr[p].lt,tr[p].addtag,tr[p].multag,tr[p].flag);
make(tr[p].rt,tr[p].addtag,tr[p].multag,tr[p].flag);
tr[p].addtag=0,tr[p].multag=1,tr[p].flag=0;
if(pos<=mid){
qry(tr[p].lt,pos,lt,mid,val);
return;
}
else{
qry(tr[p].rt,pos,mid+1,rt,val);
return;
}
}
sil void blk_init(){
t=max((int)sqrt(1.50066*m),77);
siz=m/t;
for(int i=1;i<=t;i++){
L[i]=(i-1)*siz+1;
R[i]=i*siz;
}
if(R[t]<m){
t++;
L[t]=R[t-1]+1;
R[t]=m;
}
for(int i=1;i<=t;i++)
for(int j=L[i];j<=R[i];j++)
pos[j]=i;
}
sil void dsc_init(){
for(int i=1;i<=m+1;i++)
fa[i]=i;
for(int i=1;i<=t;i++){
g[i][++tot[i]]=n;
for(int j=L[i];j<=R[i];j++){
if(q[j].op<3){
g[i][++tot[i]]=q[j].l;
g[i][++tot[i]]=q[j].r;
g[i][++tot[i]]=q[j].l-1;
g[i][++tot[i]]=q[j].r-1;
}
}
sort(g[i]+1,g[i]+tot[i]+1);
tot[i]=unique(g[i]+1,g[i]+tot[i]+1)-g[i]-1;
for(int j=L[i];j<=R[i];j++){
if(q[j].op<3){
q[j].l=ask(q[j].l,i);
q[j].r=ask(q[j].r,i);
}
else
fa[j]=fnd(j+1);
}
root[i]=build(1,tot[i]);
}
}
signed main(){
//freopen("P3710_1.txt","r",stdin);
//freopen("gogogo.txt","w",stdout);
n=read(),m=read();
for(int i=1;i<=m;i++){
q[i].op=read();
if(q[i].op<=2){
q[i].l=read(),q[i].r=read(),q[i].d=read();
q[i].d=(q[i].d>MOD?q[i].d-MOD:q[i].d);
}
if(q[i].op>=3)
q[i].p=read();
}
blk_init();
dsc_init();
for(int i=1;i<=m;i++){
if(q[i].op==1)
upd_add(root[pos[i]],q[i].l,q[i].r,q[i].d,1,tot[pos[i]]);
else if(q[i].op==2)
upd_mul(root[pos[i]],q[i].l,q[i].r,q[i].d,1,tot[pos[i]]);
else if(q[i].op==3){
long long ans=0;
for(int j=1;j<pos[i];j++)
qry(root[j],ask(q[i].p,j),1,tot[j],ans),ans%=MOD;
for(int j=fnd(L[pos[i]]);j<=i;j=fnd(j+1)){
int cur=ask(q[i].p,pos[i]);
if(q[j].op==1&&q[j].l<=cur&&cur<=q[j].r)
ans+=q[j].d,ans=(ans>MOD?ans-MOD:ans);
if(q[j].op==2&&q[j].l<=cur&&cur<=q[j].r)
ans*=q[j].d,ans%=MOD;
}
write(ans%MOD),putchar('\n');
}
else{
fa[q[i].p]=fnd(q[i].p+1);
make(root[pos[q[i].p]],0,1,1);
int lt=fnd(L[pos[q[i].p]]),rt=min(R[pos[q[i].p]],i);
for(int j=lt;j<=rt;j=fnd(j+1)){
if(q[j].op==1)
upd_add(root[pos[q[i].p]],q[j].l,q[j].r,q[j].d,1,tot[pos[q[i].p]]);
if(q[j].op==2)
upd_mul(root[pos[q[i].p]],q[j].l,q[j].r,q[j].d,1,tot[pos[q[i].p]]);
}
}
}
return 0;
}
/*
500 24
1 150 496 322370810
4 1
1 188 219 961522103
1 131 493 209961579
2 85 408 591218226
2 317 446 129855108
3 306
1 26 360 576330848
1 211 307 1063082822
2 181 344 117319866
3 388
3 461
3 306
2 317 443 649178399
2 223 346 46701898
1 14 276 859673089
1 18 308 849166726
2 29 315 1037051746
3 436
1 70 89 528712138
1 34 355 859799420
3 39
2 11 34 786295242
3 367
*/
下辈子再也不会做这种题了吗的。
总结:
-
分块题先想暴力,再缩小范围。
-
对于操作序列分块的思想很重要。

浙公网安备 33010602011771号