Loading

浅谈分块和莫队基础知识

好久没有水一水数据结构了

LOJ 数列分块入门 2

这道题题意很简单,就是要写一个支持区间加法和区间查询小于 \(c^2\) 个数的数据结构。

考虑分块来做,对于区间加法可以直接用 \(lazy\) 标记维护块内和;对于查询,可以考虑对每个块中的按大小排好序后的数列二分找小于 \(c^2\) 的个数,单次修改查询时间复杂度均为 \(O(\sqrt{n}*log_2\sqrt n+\sqrt{n})\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int ans=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
	return ans*f;
}
const int N=5e4+5;
int T,n,a[N],lazy[N],block,belong[N],L[N],R[N],f[N];
void sortt(int i){
	int tem[block+5],cnt=0;
	for(int j=L[i];j<=R[i];j++) tem[++cnt]=a[j];
	sort(tem+1,tem+1+cnt);
	cnt=0;
	for(int j=L[i];j<=R[i];j++) f[j]=tem[++cnt];
}
void add(int l,int r,int c){
	if(belong[l]==belong[r]){
		for(int i=l;i<=r;i++) a[i]+=c;
		sortt(belong[l]);
	}
	else{
		for(int i=belong[l]+1;i<=belong[r]-1;i++) lazy[i]+=c;
		for(int i=l;i<=R[belong[l]];i++) a[i]+=c;
		for(int i=L[belong[r]];i<=r;i++) a[i]+=c;
		sortt(belong[l]);
		sortt(belong[r]);
	}
}
int query(int l,int r,int c){
	if(belong[l]==belong[r]){
		int res=0;
		for(int i=l;i<=r;i++) if(a[i]+lazy[belong[i]]<c) res++;
		return res;
	}
	else{
		int res=0;
		for(int i=l;i<=R[belong[l]];i++) if(a[i]+lazy[belong[i]]<c) res++;
		for(int i=L[belong[r]];i<=r;i++) if(a[i]+lazy[belong[i]]<c) res++;
		for(int i=belong[l]+1;i<=belong[r]-1;i++){
			int ll=1,rr=R[i]-L[i]+1,mid,ans=0;
			while(ll<=rr){
				mid=(ll+rr)>>1;
				if(f[L[i]+mid-1]+lazy[i]<c) ans=mid,ll=mid+1;
				else rr=mid-1;
			}
			res+=ans;
		}
		return res;
	}
}
signed main(){
	T=n=read(),block=sqrt(n);
	for(int i=1;i<=n;i++)	a[i]=read(),belong[i]=(i-1)/block+1;
	for(int i=1;i<=belong[n];i++) L[i]=R[i-1]+1,R[i]=min(n,L[i]+block-1),sortt(i);
	while(T--){
		int opt=read(),x=read(),y=read(),z=read();
		if(!opt) add(x,y,z);
		else printf("%lld\n",query(x,y,z*z));
	}
	return 0;
}

LOJ 数列分块入门 5

这道题是区间开根号下取整(下文开根号均指开根号下取整),不难发现对于 \(2^{32}\) 只需要开 \(6\) 次就为 \(1\) 了,所以在 \(int\) 类型里的非负整数开了 \(6\) 及以上次根号,结果肯定为 \(1\),所以我们只用处理开根号次数小于 \(6\) 的数。

先分块,考虑用一个 \(sum\) 统计区间和,然后再对于每一次开根号操作,都是暴力修改,因为最多总共修改 \(5n\) 次,所以时间可以过。查询的时候就可以直接用查询区间和的方法写。

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int ans=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
	return ans*f;
}
const int N=5e4+5;
int n,T,a[N],sum[N],lazy[N],belong[N],block,L[N],R[N];
void dosqrt(int l,int r){
	if(belong[l]==belong[r]){
		for(int i=l;i<=r;i++){
			sum[belong[i]]-=a[i];
			a[i]=sqrt(a[i]);
			sum[belong[i]]+=a[i];
		}
	}
	else{
		for(int i=belong[l]+1;i<=belong[r]-1;i++){
			if(sum[i]==R[i]-L[i]+1) continue;
			for(int j=L[i];j<=R[i];j++){
				sum[i]-=a[j];
				a[j]=sqrt(a[j]);
				sum[i]+=a[j];
			}
		}
		for(int i=l;i<=R[belong[l]];i++){
			sum[belong[i]]-=a[i];
			a[i]=sqrt(a[i]);
			sum[belong[i]]+=a[i];
		}
		for(int i=L[belong[r]];i<=r;i++){
			sum[belong[i]]-=a[i];
			a[i]=sqrt(a[i]);
			sum[belong[i]]+=a[i];
		}
	}
}
int query(int l,int r){
	if(belong[l]==belong[r]){
		int res=0;
		for(int i=l;i<=r;i++) res+=a[i];
		return res;
	}
	else{	
		int res=0;
		for(int i=l;i<=R[belong[l]];i++) res+=a[i];
		for(int i=L[belong[r]];i<=r;i++) res+=a[i];
		for(int i=belong[l]+1;i<=belong[r]-1;i++) res+=sum[i];
		return res;
	}
}
int main(){
	T=n=read();
	block=sqrt(n);
	for(int i=1;i<=n;i++){
		a[i]=read();
		belong[i]=(i-1)/block+1;
		sum[belong[i]]+=a[i];
	}
	for(int i=1;i<=belong[n];i++) L[i]=R[i-1]+1,R[i]=min(L[i]+block-1,n);
	while(T--){
		int opt=read(),l=read(),r=read(),c=read();
		if(opt) printf("%d\n",query(l,r));
		else dosqrt(l,r);
	}
	return 0;
}

Luogu P3901 数列找不同

这道题可以用莫队来做,所谓莫队算法都要满足一个最基本的性质,就是知道一个区间,可以 \(O(1)\) 的求出他增加或减少一个元素的区间,这道题就可以使用莫队算法。

考虑双指针来做,用 \(L\) 表示左端点,用 \(R\) 表示右端点,开个桶 \(vis\) 来表示当前 \([L,R]\) 中,\(i\) 这个数出现了几次,初始值 \(L=1,R=0\)。很显然,当区间扩大时,如果有一个 \(vis\)\(0\) 变成了 \(1\),就是有一个数不同了;当区间减小时,如果有一个 \(vis\)\(1\) 变成 \(0\) 了,就是有一个不同没了。

单独这样对与每一个每一个询问的 \(l,r\) 都做一遍这样的操作无疑是 \(n^2\) 的,但是如果我们转为离线做法呢?我们可以调整查询区间的顺序,让 \(L,R\) 不用从头再来一边重复的了,所以我们就有了 \(O(n+mlog_2m)\) 的算法

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int ans=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
	return ans*f;
}
const int N=1e5+5;
int n,m,a[N],vis[N],block,ans,t[N];
struct lzz{
	int l,r,id;
}q[N];
bool cmp(lzz x,lzz y){
	if(x.l/block==y.l/block) return x.r<y.r;
	return x.l/block<y.l/block;
}
void del(int x){
	vis[a[x]]--;
	if(vis[a[x]]==0) ans--;
}
void add(int x){
	vis[a[x]]++;
	if(vis[a[x]]==1) ans++;
}
int main(){
	n=read(),m=read();block=sqrt(n);
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
	sort(q+1,q+1+m,cmp);
	int L=1,R=0;
	for(int i=1;i<=m;i++){
		while(L<q[i].l) del(L++);
		while(L>q[i].l) add(--L);
		while(R<q[i].r) add(++R);
		while(R>q[i].r) del(R--);
		if(ans==q[i].r-q[i].l+1) t[q[i].id]=1;
	}
	for(int i=1;i<=m;i++){
		if(t[i]) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

Luogu P1494 [国家集训队]小Z的袜子

真正的莫队入门经典题,和上面的思路其实是差不多的。

设区间里有 \(a_1,a_2,a3,...,a_n\) 双不同颜色的袜子,区间内的贡献计算是

\[\frac{C^2_{a_1}+C^2_{a_2}+C^2_{a_3}+...+C^2_{a_n}}{C^2_{a_1+a_2+a_3+...+a_n}} \]

然后可以发现,当区间扩大时,分子就减去之前和加入袜子相同颜色的组合数,加上添加这双袜子后这种颜色袜子的组合数;当区间减小时,就是减去了。

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int ans=0,f=1;char ch=getchar();
	while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}
	while(isdigit(ch)){ans=(ans<<3)+(ans<<1)+ch-48;ch=getchar();}
	return ans*f;
}
const int N=5e4+5;
int n,m,a[N],vis[N],block,ans,t1[N],t2[N];
struct lzz{
	int l,r,id;
}q[N];
bool cmp(lzz x,lzz y){
	if(x.l/block==y.l/block) return x.r<y.r;
	return x.l/block<y.l/block;
}
int gcd(int x,int y){
	return !y ? x : gcd(y,x%y);
}
int C(int x){
	return x*(x-1)/2;
}
void del(int x){
	vis[a[x]]--;
	ans=ans-C(vis[a[x]]+1)+C(vis[a[x]]);
}
void add(int x){
	vis[a[x]]++;
	ans=ans-C(vis[a[x]]-1)+C(vis[a[x]]);
}
signed main(){
	n=read(),m=read();block=sqrt(n);
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
	sort(q+1,q+1+m,cmp);
	int L=1,R=0;
	for(int i=1;i<=m;i++){
		while(L<q[i].l) del(L++);
		while(L>q[i].l) add(--L);
		while(R<q[i].r) add(++R);
		while(R>q[i].r) del(R--);
		t1[q[i].id]=ans;
		t2[q[i].id]=C(q[i].r-q[i].l+1);
	}
	for(int i=1;i<=m;i++){
		if(t1[i]==0) printf("0/1\n");
		else{
			int d=gcd(t1[i],t2[i]);
			printf("%lld/%lld\n",t1[i]/d,t2[i]/d);
		}
	}
	return 0;
}
posted @ 2021-01-21 22:31  Quick_Kk  阅读(129)  评论(0编辑  收藏  举报