莫队算法

借鉴:大佬1大佬2

通过对离线问题的处理是莫队算法的核心

数据结构简化操作一:优化

通过对a[L-1]和a[R+1]的值对答案的影响的处理,来扩大区间

如果[L,R]要变成[l,r],需要判断|l-L|+|r-R|,也就是曼哈顿距离。

数据结构简化操作二:分块。

分成根号n个块,把所有询问按左端点放进各自的块内,对于每个块按右端点排序。

考虑总体复杂度:

对于每个块内右端点的移动,是n级别的移动,一共有根号n个块。复杂度是n根号n;

左端点的移动是M次询问*根号n【也有可能是n根号n,如果只有两次询问但跨越了比较多】

总体加起来=n根号n

小Z的袜子【模板】

题意:对多个询问,询问区间里选出两个数,两个数相同的概率。

题解:

P=C(A1,2)+C(A2,2)+C(A3,3)+...+C(An,n)/C(n,2)

=[A1(A1-1)/2+A2(A2-1)+...An(An-1)/2]/[n*(n-1)/2]

=[A1^2+A2^2+...+An^2-n]/[n*(n-1)/2]

维护区间内数量和即可。

代码如下:

#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l;i<=r;i++) 
using namespace std;

int n,m;
int A[50005];
int M[50005];
int pos[50005];
long long mid=0,L=1,R=1;


void add(int x){
	mid-=M[x]*M[x];
	M[x]++;
	mid+=M[x]*M[x];	
}

void del(int x){
	mid-=M[x]*M[x];
	M[x]--;
	mid+=M[x]*M[x];
}

struct node{
	long long  l,r;
	int index;
	void inq(){scanf("%lld%lld",&l,&r);}
	friend bool operator < (node a,node b){
		if(pos[a.l]!=pos[b.l])return a.l<b.l;
		else return a.r<b.r;
	}
}Q[50005],C[50005];

int main(){
	cin>>n>>m;	
	FOR(i,1,n)scanf("%d",&A[i]);
	int block=sqrt(n);
	FOR(i,1,n){
		pos[i]=i/block;
	}
	FOR(i,1,m){
		Q[i].inq();
		Q[i].index=i;
	}
	sort(Q+1,Q+1+m);
	mid=1;
	M[A[1]]++;
	FOR(i,1,m){
		while(L>Q[i].l){
			L--;
			add(A[L]);	
		}
		while(L<Q[i].l){
			del(A[L]);
			L++;
		}
		while(R<Q[i].r){
			R++;
			add(A[R]);
		}
		while(R>Q[i].r){
			del(A[R]);
			R--;
		}	
		long long li=mid-(Q[i].r-Q[i].l+1);
		long long lm=(Q[i].r-Q[i].l+1)*(Q[i].r-Q[i].l);
		if(li==0)C[Q[i].index].l=0,C[Q[i].index].r=1;
		else{
			int gcd=__gcd(li,lm);
			C[Q[i].index].l=li/gcd,C[Q[i].index].r=lm/gcd;
		}
	}
	FOR(i,1,m){
		cout<<C[i].l<<"/"<<C[i].r<<endl;
	}
}

可以看出来:代码的核心在于分块。

1_处理排序中对块的判断。

2_考虑到左右区间的移动。

训练:

Powerful array

题意:维护区间询问:种类数量和平方*自身种类的和。

题解:同小Z的袜子。主要维护数量的数组,每次add和del函数进行更新。

#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l;i<=r;i++) 
#define LL long long
using namespace std;

const int maxn = 200050;

int A[maxn];
int pos[maxn];
int M[1000050];
int n,m;
long long mid=0;
long long B[maxn];

void init(){
	int block=sqrt(n);
	FOR(i,1,n){
		pos[i]=i/block;
	}
}

void add(int x){
	LL st=M[x];
	mid-=st*st*(LL)x;
	M[x]++;
	mid+=(st+1)*(st+1)*(LL)x;
}

void del(int x){
	LL st=M[x];
	mid-=st*st*(LL)x;
	M[x]--;
	mid+=(st-1)*(st-1)*(LL)x;
}

struct node{
	int l,r;
	int index;
	void inp(int now){scanf("%d%d",&l,&r);index=now;}
	friend bool operator < (node a,node b){
		if(pos[a.l]!=pos[b.l])return a.l<b.l;
		else return a.r<b.r;
	}
}Q[maxn];


int main(){
	cin>>n>>m;
	FOR(i,1,n)scanf("%d",&A[i]);
	init();
	FOR(i,1,m)Q[i].inp(i);
	sort(Q+1,Q+1+m);
	int L=1,R=1;
	M[A[1]]++;
	mid=1*1*A[1];
	FOR(i,1,m){
		while(L<Q[i].l){
			del(A[L]);
			L++;
		}
		while(L>Q[i].l){
			L--;
			add(A[L]);
		}
		while(R<Q[i].r){
			R++;
			add(A[R]);
		}
		while(R>Q[i].r){
			del(A[R]);
			R--;
		}
		B[Q[i].index]=mid;		
	}
	FOR(i,1,m){
		cout<<B[i]<<endl;
	}
}

有趣的结合题

题意:多个询问,对给定区间内,判断在[a,b]中的数有多少个,数的种类有多少个。

题解:要求两个答案其实异曲同工。

每次加数量的时候,判断是不是从0加到1,从而对种类修改。

对于给定区间,[a,b]的答案,我们可以用分块来求解:每个块的数有多少个和种类被记录后求解的复杂度只有根号n。

对于多个区间,我们需要维护每个块的数和种类,这个时候需要维护每个数出现的cnt,每次对cnt修改的时候,同时对每个块的记录进行修改。【显然莫队】

【莫队加分块:加快读快写可以快400ms,平均下来应该是200ms】

算是分块+莫队的各自模板题。

	#include<bits/stdc++.h>
	#define FOR(i,l,r) for(int i=l;i<=r;i++) 
	#define LL long long
	using namespace std;
	
	int n,m;
	const int maxn = 100010;
	const int maxm = 200010;
	int A[maxn],B[maxn],C[maxn];
	int cnt[maxm],pos[maxm];
	int all[maxm],num[maxm]; 
	int block;
	
	inline int read() {
	    int num=0, w=0;
	    char ch=0;
	    while (!isdigit(ch)) {
	        w|=ch=='-';
	        ch = getchar();
	    }
	    while (isdigit(ch)) {
	        num = (num<<3) + (num<<1) + (ch^48);
	        ch = getchar();
	    }
	    return w? -num: num;
	}
	
	inline void write(int x)
	{
	    if(x<0) {
	        putchar('-');
	        x = -x;
	    }
	    if(x>9) write(x / 10);
	    putchar(x % 10 + '0');
	}
	
	void init(){
		block=sqrt(n);
		FOR(i,1,n)pos[i]=(i-1)/block+1;
	} 
	
	void add(int x){
		++cnt[x];
		if(cnt[x]==1)++num[pos[x]];
		++all[pos[x]];
	}
	
	void del(int x){
		--cnt[x];
		if(cnt[x]==0)--num[pos[x]];
		--all[pos[x]];	
	}
	
	struct node{
		int l,r,a,b;
		int index;
		void inp(int ind){l=read();r=read();a=read();b=read();index=ind;} 
		friend bool operator < (node a,node b){
			if(pos[a.l]!=pos[b.l])return pos[a.l]<pos[b.l];
			else return a.r<b.r;
		}	
	}Q[maxn];
	
	void cal(int u){
		int start=Q[u].a,end=Q[u].b,ind=Q[u].index;
		if(pos[start]==pos[end]){
			for(int i=start;i<=end;i++){
				B[ind]+=cnt[i];
				if(cnt[i])C[ind]++;
			}
		}
		else{
			for(int i=start;i<=pos[start]*block;i++){B[ind]+=cnt[i];if(cnt[i])C[ind]++;}
			for(int i=pos[start]+1;i<pos[end];i++){B[ind]+=all[i];C[ind]+=num[i];} 
			for(int i=(pos[end]-1)*block+1;i<=end;i++){B[ind]+=cnt[i];if(cnt[i])C[ind]++;}
		}
	}
	
	int main(){ 
		cin>>n>>m;
		init();
		FOR(i,1,n)A[i]=read();
		FOR(i,1,m){
			Q[i].inp(i);
		}
		int L=1,R=0;
		sort(Q+1,Q+1+m);
		FOR(i,1,m){
			while(L<Q[i].l){
				del(A[L]);
				++L;
			};
			while(L>Q[i].l){
				--L;
				add(A[L]);
			};
			while(R<Q[i].r){
				++R;
				add(A[R]);
			};
			while(R>Q[i].r){
				del(A[R]);
				--R;
			};	
			cal(i);	
		}
		FOR(i,1,m){
			write(B[i]);
			printf(" ");
			write(C[i]);
			printf("\n");
		}
	}
		

这是对以前莫队的一次更新,算是复习。

如果还有基础莫队的好题,我会继续加上来。

如果准备学习进阶莫队了,我会单独再开一个版块。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值