普通莫队算法

简介

莫队是什么

莫队算法是由莫涛提出的算法。在莫涛提出莫队算法之前,莫队算法已经在 Codeforces 的高手圈里小范围流传,但是莫涛是第一个对莫队算法进行详细归纳总结的人。莫涛提出莫队算法时,只分析了普通莫队算法,但是经过 OIer 和 ACMer 的集体智慧改造,莫队有了多种扩展版本。(来自oiwiki)

莫队是干什么用的

莫队是一种可以处理序列上问题的离线算法
它的适用性很强,强到可以解决绝大多数无修改的不强制在线的序列上查询问题
总之,这是一种极为优美的暴力

为什么选择莫队

第一个原因我们在上面已经提到了,是因为适用性强,暂且不表
第二个原因就是因为它真的够快,O(n√n)对于大多数想让你AC的题目来说已经足够快了

普通莫队

原理

显然对于一个区间[l,r]我们可以O(1)暴力扩展到[l+1,r]或[l-1,r]或[l,r+1]或[l,r-1],那么我们在将数据离线后以l为第一关键字、r为第二关键字排序,按顺序暴力从上一次询问转移到下一次询问,对于所有询问我们可以在O(n√n)的复杂度下解决

时间复杂度证明

现在我们设有q个询问,区间长度为n,块长度为len,则对于所有块在最坏的情况下时间复杂度为O(qlen+n2/len)
其中len的大小是我们需要确定的
利用均值不等式,q
len+n2/len<=2√q*n2=2n√q
那么对于时间复杂度则为O(n√q)
证毕

模板

void add(int i){
    
    /*update ans*/}
void del(int i){
    
    /*update ans*/}
void solve(){
    
    
	sort(a+1,a+m+1);
	for(int i=1,l=1,r=0;i<=m;i++){
    
    
		if(a[i].l==a[i].r){
    
    ans[a[i].id]=0;continue;}
		while(l>a[i].l)add(q[--l]);
		while(r<a[i].r)add(q[++r]);
		while(l<a[i].l)del(q[l++]);
		while(r>a[i].r)del(q[r--]);
		//update ans;
	}
}

小Z的袜子

思路

这是一道莫队模板题,每进行一次操作时,统计当前块中相同个数的数量以及当前数,概率可分别计算分子和分母,分子为总数,分母为(r-l+1)*(r-l)/2

代码

#include<bits/stdc++.h>
#define N 50005
#define ll long long
using namespace std;
inline ll read(){
    
    
	ll x=0,f=1;char c=getchar();
	while(!isdigit(c)){
    
    if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){
    
    x=x*10+c-'0';c=getchar();}
	return x*f;
}
ll ans1[N],ans2[N],cnt[N],q[N],maxn,sum;
struct query{
    
    
	ll l,r,id;
	bool operator<(const query &x)const{
    
    
		if(l/maxn!=x.l/maxn)return l<x.l;
		return (l/maxn)&1?r<x.r:r>x.r;
	}
}a[N];
inline void add(int i){
    
    sum+=cnt[i];cnt[i]++;}
inline void del(int i){
    
    cnt[i]--;sum-=cnt[i];}
ll gcd(ll a,ll b){
    
    return b?gcd(b,a%b):a;}
int main(){
    
    
	int n=read(),m=read();maxn=sqrt(n);
	for(int i=1;i<=n;i++)q[i]=read();
	for(int i=1;i<=m;i++){
    
    ll l=read(),r=read(),id=i;a[i]=(query){
    
    l,r,id};}
	sort(a+1,a+1+m);
	for(int i=1,l=1,r=0;i<=m;i++){
    
    
		if(a[i].l==a[i].r){
    
    ans1[a[i].id]=0;ans2[a[i].id]=1;continue;}
		while(l>a[i].l)add(q[--l]);
		while(r<a[i].r)add(q[++r]);
		while(l<a[i].l)del(q[l++]);
		while(r>a[i].r)del(q[r--]);
		ans1[a[i].id]=sum;ans2[a[i].id]=(ll)(r-l+1)*(r-l)/2;
	}
	for(int i=1;i<=m;i++){
    
    
		if(ans1[i]){
    
    ll g=gcd(ans1[i],ans2[i]);ans1[i]/=g,ans2[i]/=g;}
		else ans2[i]=1;
		printf("%lld/%lld\n",ans1[i],ans2[i]);
	}
	return 0;
}

易犯错误

由于之前我们已经对序列排好序了,所以这里我们应该使用原编号进行查询,而不是排序后的序号

猜你喜欢

转载自blog.csdn.net/MuLaSaMe/article/details/118195534