分块

分块#

超级暴力,超好想,超好打,超好用

无脑首选

放个题单

下面的例题较简单的就不放代码

#6277. 数列分块入门 1#

区间加法单点求值

边角块暴力,区间打标记

时间复杂度 O((n+m)n)

#6278. 数列分块入门 2#

区间加法,查询区间内小于某一个数的个数

对于每一个块我们维护块内有序

修改时边角块暴力修改,然后暴力重排,区间直接打标记

查询时边角块暴力,区间直接二分 (因为每个块都是有序的)

时间复杂度 O((n+m)nlogn)

#define rep(i,l,r) for(register int i=l;i<=r;++i)
int n,size,a[N],bl[N],tag[N];
vector <int> p[505];
inline void rebuild(int x){
    p[x].clear();
    for(int i=(x-1)*size+1;i<=min(x*size,n);i++) 
        p[x].push_back(a[i]);
    sort(p[x].begin(),p[x].end());
}
inline void add(int l,int r,int val){
    for(int i=l;i<=min(bl[l]*size,r);i++) a[i]+=val;
    rebuild(bl[l]);
    if(bl[l]!=bl[r]){
        for(int i=(bl[r]-1)*size+1;i<=r;i++) a[i]+=val;
        rebuild(bl[r]);
    }
    for(int i=bl[l]+1;i<=bl[r]-1;i++) tag[i]+=val;
}
inline int work(int l,int r,int c){
    int ans=0;
    for(int i=l;i<=min(bl[l]*size,r);i++) 
        if(a[i]+tag[bl[l]]<c) ans++;
    if(bl[l]!=bl[r]) 
        for(int i=(bl[r]-1)*size+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(p[i].begin(),p[i].end(),x)-p[i].begin();
    }
    return ans;
}
int main(){
    n=read();size=sqrt(n);
    rep(i,1,n) a[i]=read();
    rep(i,1,n){bl[i]=(i-1)/size+1;p[bl[i]].push_back(a[i]);}
    for(int i=1;i<=bl[n];i++) sort(p[i].begin(),p[i].end());
    rep(i,1,n){
        int f=read(),x=read(),y=read(),val=read();
        if(!f) add(x,y,val);
        else cout<<work(x,y,val*val)<<endl;
    }
    return 0;
}

#6279. 数列分块入门 3#

区间加,查询某个数的前驱/后继

对于每一个块我们维护块内有序

修改时边角块暴力修改,然后暴力重排,区间直接打标记

查询时边角块暴力,区间直接二分 (因为每个块都是有序的)

时间复杂度 O((n+m)nlogn)

#6280. 数列分块入门 4#

区间加,区间求和

区间打标记即可

时间复杂度 O((n+m)n)

#6281. 数列分块入门 5#

区间开方,区间查询

这东西暴力搞就行,能过

#6282. 数列分块入门 6#

单点插值,单点查询

显然我们先分块,然后暴力插,每 n 次插入就重构

时间复杂度 O((n+m)n)

int n,cnt,t,sz,mx;
int a[N],bl[N];
vector <int> p[N];

inline void build(int op){
	sz=sqrt(t);
	int tot=0;
	if(op){
		for(int i=1;i<=mx;++i){
			for(auto x:p[i])
				a[++tot]=x;
			p[i].clear();
		}
	}
	for(int i=1;i<=t;++i){
		bl[i]=(i-1)/sz+1;
		p[bl[i]].push_back(a[i]);
	}
	mx=bl[t];
	//if(op) db1();
}

inline void insert(int b,int x,int k){
	if(x==p[b].size()){
		p[b].push_back(x);
		return;
	}
	p[b].insert(p[b].begin()+x,k);
	if(p[b].size()>=5*sz) build(1);
}

inline void ins(int x,int y){
	for(int i=1;i<=mx;++i){
		if(p[i].size()>=x){
			insert(i,x-1,y);
			return;
		}
		else x-=p[i].size();
	}
}

inline int qry(int b,int x){
	return p[b][x-1];
}

inline int query(int x){
	for(int i=1;i<=mx;++i){
		if((int)p[i].size()>=x){
			return qry(i,x);
		}
		else x-=p[i].size();
	}
}

signed main(){
	n=read();t=n;
	for(int i=1;i<=n;++i) a[i]=read();
	build(0);
	for(int i=1;i<=n;++i){
		int op=read(),l=read(),r=read();read();
		if(op==0) ins(l,r),++cnt,++t;
		else printf("%d\n",query(r));
	}
}

#6283. 数列分块入门 7#

区间乘法,区间加法,单点查询

显然先乘后加

#6284. 数列分块入门 8#

区间询问等于一个数 c 的元素有多少个,并将这个区间的所有元素改为 c

显然整块打标记,边角块暴力重构

#6285. 数列分块入门 9#

同下蒲公英

P4168 [Violet]蒲公英#

n 个数,m 次操作

每次查询区间 [l,r] 的区间众数是几

如果有多个就输出最小的那一个

强制在线

1n,m5×104 (话说为什么不能跑105)

思路

看到强制在线,看到维护众数,看到 5×104,想到分块

考虑什么样的数可以是区间内的众数:

  • 边角块的每一个数

  • 整块的众数

我们可以考虑将在询问时上述这些数加入备选答案中

若块取 n,那么最多会有 2×n1 个数,应此每个数的出现次数都必须 O(1) 算出

首先离散化

由于这题不卡空间,我们记录 cnti,j 表示前 i 个块内 j 出现的次数,numi,j 表示块 ij 的众数是哪个

那么显然 cnti,j 可以在 O(nn) 的时间内预处理

numi,j 可以为且只能为 numi1,j 或是块 i 内中的任意数 k

我们可以暴力统计哪个数出现次数最多

显然这样更新每个 numi,j 都是 O(n) 的,总共是 O(nn)

然后在询问时我们就可以利用 cntO(1) 的求出连续几个整块内每个数出现的次数,总共的复杂度是 O(mn)

这样下来总复杂度为 O((n+m)n) 可以接受

具体实现的细节看代码

code

int n,m,sz,ct;
int v[N],a[N],bl[N],L[N],R[N],cnt[S][N];
int cb[N],num[S][S];

inline int query(int x,int y){
	int l=bl[x],r=bl[y];
	vector <int> p;
	if(l==r){
		int mx=0,id=0;
		for(int i=x;i<=y;++i){
			++cb[v[i]];
			if(cb[v[i]]>mx||(cb[v[i]]==mx&&v[i]<id)) mx=cb[v[i]],id=v[i];
		}
		for(int i=x;i<=y;++i) cb[v[i]]=0;
		return id;
	}
	for(int i=x;i<=R[l];++i){
		if(!cb[v[i]])
			p.push_back(v[i]);
		++cb[v[i]];
	}
	for(int i=L[r];i<=y;++i){
		if(!cb[v[i]])
			p.push_back(v[i]);
		++cb[v[i]];
	}
	if(l==r-1){
		int mx=0,id=0;
		for(auto q:p){
			if(cb[q]>mx||(cb[q]==mx&&q<id)) mx=cb[q],id=q;
			cb[q]=0;
		}
		return id;
	}
	int id=num[l+1][r-1],mx=cb[id]+cnt[r-1][id]-cnt[l][id];
	cb[id]=0;
	for(auto q:p){
		if(q==id) continue;
		int now=cnt[r-1][q]-cnt[l][q]+cb[q];
		if(now>mx||(now==mx&&q<id)) mx=now,id=q;
		cb[q]=0;
	}
	return id;
}

signed main(){
	n=read(),m=read();
	sz=sqrt(n);
	for(int i=1;i<=n;++i){
		v[i]=read();a[i]=v[i];
		bl[i]=(i-1)/sz+1;
		if(bl[i]!=bl[i-1]) L[bl[i]]=i,R[bl[i-1]]=i-1;
	}
	R[bl[n]]=n;
	sort(a+1,a+n+1);
	ct=1;
	for(int i=2;i<=n;++i)
		if(a[i]!=a[i-1]) a[++ct]=a[i];
	for(int i=1;i<=n;++i)
		v[i]=lower_bound(a+1,a+ct+1,v[i])-a;
	for(int i=1;i<=bl[n];++i){
		for(int j=0;j<=ct;++j) cnt[i][j]=cnt[i-1][j];
		for(int j=L[i];j<=R[i];++j)
			++cnt[i][v[j]];
	}
	for(int i=1;i<=bl[n];++i)
		for(int j=1;j<=i;++j){
			int t=num[j][i-1],mx=(t!=0)*(cnt[i][t]-cnt[j-1][t]);
			for(int k=L[i];k<=R[i];++k){
				if(cnt[i][v[k]]-cnt[j-1][v[k]]>mx||(cnt[i][v[k]]-cnt[j-1][v[k]]==mx&&v[k]<t))
					mx=cnt[i][v[k]]-cnt[j-1][v[k]],t=v[k];
			}
			num[j][i]=t;
		}
	int x=0;
	while(m--){
		int l=read(),r=read();
		l=(l+x-1)%n+1;
		r=(r+x-1)%n+1;
		if(l>r) swap(l,r);
		x=a[query(l,r)];
		printf("%d\n",x);
	}
}

P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III#

给你一个长为 n 的序列 am 次询问,每次查询一个区间的众数的出现次数,强制在线。

思路

这题卡了空间,我们需要另辟蹊径

考虑每个区间 [l,r] 的众数出现次数为max{整块的众数的出现次数,边角块的数在[l,r]出现的次数}

所以我们只需要预处理每个块 i 到块 j 的众数出现次数,然后暴力搞边角块就行了

考虑边角块如何暴力

我们搞一个 vector 数组 v[a1n] 用于存储数 ai 所有出现的位置,搞一个 posi 表示 aiv[ai] 出现的位置是哪

对于左边的块,我们

int ps=pos[i];
while(ps+ans<v[a[i]].size()&&v[a[i]][ps+ans]<=y) ++ans;

对于右边的块,我们

int ps=pos[i];
while(ps-ans>=0&&v[a[i]][ps-ans]>=x) ++ans;

就是看当前的答案够不够优,如果不够就不断增加 ans

显然这样每次询问 ans 最多增加 2×n 次,可以接受

总时间复杂度是 O((n+m)n) 的,空间复杂度是 O(n) 的,可以接受

code

int n,m,sz;
int a[N],b[N],pos[N];
int bl[N],L[S],R[S],sum[N],p[S][S];
vector <int> v[N];

inline void build(){
	for(int i=1;i<=bl[n];++i){
		for(int j=i;j<=bl[n];++j){
			p[i][j]=p[i][j-1];
			for(int k=L[j];k<=R[j];++k){
				++sum[a[k]];
				p[i][j]=max(p[i][j],sum[a[k]]);
			}
		}
		memset(sum,0,sizeof(sum));
	}
	for(int i=1;i<=n;++i) v[a[i]].push_back(i),pos[i]=v[a[i]].size()-1;
}

inline int query(int x,int y){
	int l=bl[x],r=bl[y];
	int ans=0;
	if(l==r){
		for(int i=x;i<=y;++i){
			++sum[a[i]];
			ans=max(ans,sum[a[i]]);
		}
		for(int i=x;i<=y;++i)
			sum[a[i]]=0;
		return ans;
	}
	ans=p[l+1][r-1];
	for(int i=x;i<=R[l];++i){
		int ps=pos[i];
		while(ps+ans<v[a[i]].size()&&v[a[i]][ps+ans]<=y) ++ans;
	}
	for(int i=L[r];i<=y;++i){
		int ps=pos[i];
		while(ps-ans>=0&&v[a[i]][ps-ans]>=x) ++ans;
	}
	return ans;
}

signed main(){
	in>>n>>m;
	sz=sqrt(n);
	for(int i=1;i<=n;++i){
		in>>a[i];b[i]=a[i];
		bl[i]=(i-1)/sz+1;
		if(bl[i]!=bl[i-1]) L[bl[i]]=i,R[bl[i-1]]=i-1;
	}
	R[bl[n]]=n;
	stable_sort(b+1,b+n+1);
	int tot=1;
	for(int i=2;i<=n;++i)
		if(b[i]!=b[i-1]) b[++tot]=b[i];
	for(int i=1;i<=n;++i) a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
	build();
	int last=0;
	while(m--){
		int l,r;
		in>>l>>r;
		l^=last,r^=last;
		last=query(l,r);
		out<<last<<'\n';
	}
}

BZOJ 3489 A simple rmq Problem#

不知道能不能点进去

因为是 OJ上的题,就简单点好了。给出一个长度为 n 的序列,给出 M 个询问:在 [l,r] 之间找到一个在这个区间里只出现过一次的数,并且要求找的这个数尽可能大。如果找不到这样的数,则直接输出 0

强制在线

1n105,1m2×105

思路

嗯,这个题显然无法用树状数组/线段树维护,考虑分块 我是不会用树套树的

B 表示块长

考虑我们可以用 vector 维护每个权值出现的所有位置从而找到每个值上一次出现的位置 pre 和下一次出现的位置 next

显然对于每一个数 i 它对 (prei,nexti) 这个范围由贡献

考虑对于每个数 i 要对区间 [l,r] 有贡献当且仅当:

  • prei<lr<nexti

对边角块每一个数直接暴力判断其 prenext 是否不在区间内即可,这部分的时间复杂度为 O(n×B)

对整块 ij 我们显然只用考虑在 ij 内,前 2B+1 大的只出现一次的数

因为边角块最多2B 个数,显然这 2B+1 个数里面至少有一个数在 [l,r] 内只出现过一次,必然会造成贡献

我们用 fi,j,k 表示块 ij 中第 k 大的只出现一次的数,显然我们可以考虑每一个数值对所有整块的贡献

这部分时间复杂度 O(n×nB),空间复杂度 O(B3)

然后整块的每一个数的判断和边角块的每一个数的判断是一样的

Bn 这题的时间复杂度就为 O((n+m)n),空间复杂度为 O(nn),可以通过

这里我 B 取的 320

int n,m,sz,a[N],p[N];
int bl[N],d[N],L[S],R[S];
int f[S][S][2*S],id[S][S][2*S],cnt[S][S],sum[N];
vector <int> v[N];

inline void build(){
	for(int i=n;i;--i){
		for(int j=0;j<v[i].size();++j){
			int nb=bl[v[i][j]];
			int l=1,r=bl[n];
			if(j) l=bl[v[i][j-1]]+1;
			if(j<v[i].size()-1) r=bl[v[i][j+1]]-1;
			for(int k=l;k<=nb;++k)
				for(int q=nb;q<=r;++q){
					if(cnt[k][q]>sz*2) continue;
					f[k][q][++cnt[k][q]]=i;
					id[k][q][cnt[k][q]]=v[i][j];
				}
		}
	}
}

inline int query(int x,int y){
	int l=bl[x],r=bl[y];
	int mx=0,ct=0;
	if(l==r){
		for(int i=x;i<=y;++i){
			++sum[a[i]];
			if(sum[a[i]]==1) d[++ct]=a[i];
		}
		for(int i=1;i<=ct;++i){
			if(sum[d[i]]==1) mx=max(mx,d[i]);
			sum[d[i]]=0,d[i]=0;
		}
		return mx;
	}
	int tot=cnt[l+1][r-1];
	for(int i=1;i<=tot;++i){
		int now=f[l+1][r-1][i],pos=id[l+1][r-1][i];
		if((p[pos]==0||(p[pos]&&v[now][p[pos]-1]<x))&&(p[pos]==v[now].size()-1||(p[pos]<v[now].size()-1&&v[now][p[pos]+1]>y))){
			mx=now;
			break;
		}
	}
	for(int i=x;i<=R[l];++i){
		int now=a[i];
		if((p[i]==0||(p[i]&&v[now][p[i]-1]<x))&&(p[i]==v[now].size()-1||(p[i]<v[now].size()-1&&v[now][p[i]+1]>y)))
			mx=max(mx,a[i]);
	}
	for(int i=L[r];i<=y;++i){
		int now=a[i];
		if((p[i]==0||(p[i]&&v[now][p[i]-1]<x))&&(p[i]==v[now].size()-1||(p[i]<v[now].size()-1&&v[now][p[i]+1]>y)))
			mx=max(mx,a[i]);
	}
	return mx;
}

signed main(){
	read(n),read(m);
	sz=300;
	for(int i=1;i<=n;++i){
		read(a[i]);
		bl[i]=(i-1)/sz+1;
		if(bl[i]!=bl[i-1]) L[bl[i]]=i,R[bl[i-1]]=i-1;
		v[a[i]].push_back(i);
		p[i]=v[a[i]].size()-1;
	}
	R[bl[n]]=n;
	build();
	int lastans=0;
	while(m--){
		int x,y;
		read(x),read(y);
		x=(x+lastans)%n+1,y=(y+lastans)%n+1;
		if(x>y) swap(x,y);
		lastans=query(x,y);
		write(lastans);
		putchar('\n');
	}
}

作者:Into_qwq

出处:https://www.cnblogs.com/into-qwq/p/16491857.html

版权:本作品采用「qwq」许可协议进行许可。

posted @   Into_qwq  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示