莫队算法详解系列(一) 普通莫队

莫队算法详解系列(一) 普通莫队

先从一道题说起吧。

HYSBZOJ 2038 小Z的袜子(hose)

◇题目传送门◆

题目大意

给定一个区间 [ L , R ] [L,R] [L,R],输出该区间内抽出颜色相同的袜子的概率。

分析

直接暴力查找,复杂度为 O ( n 2 ) O(n^2) O(n2),在题目条件下显然超时。

考虑推一下公式:设 T i T_i Ti为第 i i i种颜色在 [ L , R ] [L,R] [L,R]中的出现的次数。

则我们易得: P ( 抽 到 相 同 颜 色 ) = ∑ T i ( T i − 1 ) ( R − L + 1 ) ( R − L ) = ∑ T i 2 − ∑ T i ( R − L + 1 ) ( R − L ) P(抽到相同颜色)=\frac{\sum{T_i(T_i-1)}}{(R-L+1)(R-L)}\\=\frac{\sum{{T_i}^2}-\sum{T_i}}{(R-L+1)(R-L)} P()=(RL+1)(RL)Ti(Ti1)=(RL+1)(RL)Ti2Ti

T i T_i Ti好维护,可 ∑ T i 2 \sum {T_i}^2 Ti2直接维护就有点麻烦了。

那我们调整一下算法顺序呢?

利用一个变量记录一下区间 [ l , r ] [l,r] [l,r] 的 ∑ T i 2 的\sum{T_i}^2 Ti2,显然从 [ l , r ] [l,r] [l,r] [ l + 1 , r ] , [ l − 1 , r ] , [ l , r + 1 ] , [ l , r − 1 ] [l+1,r],[l-1,r],[l,r+1],[l,r-1] [l+1,r],[l1,r],[l,r+1],[l,r1]的更新答案的复杂度为 O ( 1 ) O(1) O(1)

我们考虑如下算法:

Created with Raphaël 2.2.0 已知区间[l,r] 将l调至待查询区间l` 将r调至待查询区间r` 记录答案 l=l`,r=r` 是否最后一个区间 输出所有答案 结束程序 yes no

显然这个算法也是 O ( N ) O(N) O(N)的。

考虑调整查询的顺序,我们按起点将所有的查询分为 N \sqrt N N 块,每块的大小为 N N N\sqrt N NN 。所有左端点落在第一块的查询将会被先处理,而不管其右端点在哪个块。且每个块中,按右端点的升序进行排列。

这样我们就得到了莫队算法。

时间复杂度的讨论

为啥块的大小要用 N \sqrt N N

我们在计算时运用了两个指针 l , r l,r l,r,设每个块的大小为 M M M

对于指针 l l l,若两个询问不同块,那么它最多只需 O ( 2 M ) O(2M) O(2M)即可到达,故总时间为 O ( N M ) O(NM) O(NM)

对于指针 r r r,由于它几乎没有顺序,所以在计算一个块答案时,它需要移动最多 N N N次,又由于有 O ( N / M ) O(N/M) O(N/M)个块,所以总复杂度为 O ( N 2 M ) O(\frac{N^2}{M}) O(MN2)

综上,总时间复杂度为 O ( N M + N 2 M ) O(NM+\frac{N^2}{M}) O(NM+MN2),根据基本不等式,当 M = N M=\sqrt N M=N 时,莫队取得最低时间复杂度 O ( N N ) O(N\sqrt N) O(NN )。所以自然块的大小是 N \sqrt N N 的了。

参考代码

#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;

typedef long long ll;
const int Maxn=50000;

int N,Q,A[Maxn+5];
int Block;

struct Query {
	int l,r,id;
	ll a,b;
};
bool cmp_query(Query lhs,Query rhs){return (lhs.l/Block==rhs.l/Block?lhs.r<rhs.r:lhs.l/Block<rhs.l/Block);}
bool cmp_getans(Query lhs,Query rhs){return lhs.id<rhs.id;}

ll GCD(ll x,ll y) {
	if(y==0)return x;
	return GCD(y,x%y);
}

Query q[Maxn+5];
int sum[Maxn+5];
ll ans;

void add(int pos) {
	ans-=(1LL*sum[A[pos]]*sum[A[pos]]);
	sum[A[pos]]++;
	ans+=(1LL*sum[A[pos]]*sum[A[pos]]);
}
void del(int pos) {
	ans-=(1LL*sum[A[pos]]*sum[A[pos]]);
	sum[A[pos]]--;
	ans+=(1LL*sum[A[pos]]*sum[A[pos]]);
}

int main() {
	#ifdef LOACL
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	scanf("%d %d",&N,&Q);
	Block=sqrt(N);
	for(int i=1;i<=N;i++)
		scanf("%d",&A[i]);
	for(int i=1;i<=Q;i++) {
		scanf("%d %d",&q[i].l,&q[i].r);
		q[i].id=i;
	}
	sort(q+1,q+Q+1,cmp_query);
	int l=0,r=0;
	for(int i=1;i<=Q;i++) {
		while(l<q[i].l)
			del(l),l++;
		while(l>q[i].l)
			l--,add(l);
		while(r<=q[i].r)
			add(r),r++;
		while(r>q[i].r+1)
			r--,del(r);
		q[i].a=ans-(q[i].r-q[i].l+1);
		q[i].b=1LL*(q[i].r-q[i].l+1)*(q[i].r-q[i].l);
		ll tmp=GCD(q[i].a,q[i].b);
		q[i].a/=tmp,q[i].b/=tmp;
	}
	sort(q+1,q+Q+1,cmp_getans);
	for(int i=1;i<=Q;i++)
		printf("%lld/%lld\n",q[i].a,q[i].b);
	return 0;
}

总结

莫队对问题的要求是非常严格的,具体来说问题应满足如下条件:

  1. 离线查询;
  2. 时间复杂度较高时能够承受;
  3. 推出相邻情况的复杂度为常数;
  4. 由于利用分块,若分块爆了,它也会跟着爆。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值