关于莫队算法的高维扩展

让我们直接引入一道例题。


「AHOI2013」作业

题意

对长度为 \(N\) 的序列 \(A\) 提出 \(M\) 个询问,每次询问给出 \(l,r,a,b\),输出 \(\sum\limits_{i=l}^{r}[a\le A_i\le b]\)

\(N\leq 100000,M\leq 100000\),读入的数字均为 \([1,10^5]\) 内的正整数。

(注:\([a\le A_i\le b]\) 表示当 \(a\le A_i\le b\) 满足时为 \(1\),否则为 \(0\)。)


做法

常见的网络题解都说用分块+莫队。

那也确实,我们对原序列的值域进行分块,这样就可以只维护 \(l,r\) 两维。

也就是说,对 \(l,r\) 进行排序,按照正常莫队的做法,不过将扩展换成一个在分块上的算法即可。

可是,我们要看的是另一种做法。

我们先来理解普通莫队:


普通莫队

https://s3.bmp.ovh/imgs/2024/02/02/6cd5bc735d1f24e9.png

我们可以建系,将一个询问区间的两个参数认作横坐标与纵坐标(就像上面的那些奇怪的蓝点)。

此时很容易想到两个询问之间的转换即为 \(\mathcal{Θ}(dis\times times)\),其中 \(dis\) 是指在坐标系上两点的曼哈顿距离,\(times\) 指单次扩展所需的时间复杂度。

为什么呢?

举个例子:

若我们已知一个区间 \([l,r]\) 的答案时,可以推出 \([l±1,r]\)\([l,r±1]\) 的答案,

那么当我们要从 \([l_i,r_i]\) 推到 \([l_j,r_j]\) 的时候显然要经过 \(|l_i-l_j|+|r_i-r_j|\) 次推导。

\(|l_i-l_j|+|r_i-r_j|\) 正是曼哈顿距离的公式,不是么?

相信此时就不难理解了,我们因此得到两个莫队算法题目的特性:

  • 每次对区间 \([l,r]\) 进行询问。

  • 已知一个区间 \([l,r]\) 的答案时,可以推出 \([l±1,r]\)\([l,r±1]\) 的答案。

对于此类题目,我们就可以选择使用莫队算法。

但……

上面这两个特性其实并不完全。


莫队的高维扩展

有些时候,我们的询问不再是简单的区间 \([l,r]\) ……

有时候可能是对于一个函数的求解:\(f(t_1,t_2,\cdots,t_d)\)

如果在已知 \(f(t_1,t_2,...,t_d)\) 的时候,我们可以推出 \(f(t_1±1,t_2,...,t_d),f(t_1,t_2±1,...,t_d),\cdots,f(t_1,t_2,...,t_d±1)\) 的答案……

那么,我们是不是可以使用 莫队的思想 进行求解呢?

对函数 \(f(t_1,t_2,\cdots,t_d)\) 建一个 \(d\) 维坐标系,此时我们同样需要将其排序使得曼哈顿距离之和尽可能小。

按照莫队的思想将其进行排序以优化复杂度。

没错,就是这样!

莫队算法成功被我们扩展到了 \(d\) 维的情况!

带修莫队也随之而出了:当 修改的时间复杂度和推导同级 的时候,将时间作为一个维度即可。

这时候我们再来算一下时间复杂度?

设分块的长度为 \(S\)

显然前 \(d-1\) 维每次都只会推到最多 \(S\) 次,时间复杂度 \(\mathcal{O}(mS)\)

对于最后一维,实际上在每一个块内最多移动 \(m\) 次,对应的时间复杂度也就是块数乘上 \(m\)

也就是说最后的时间复杂度为 \(\mathcal{Θ}((d-1)mS+\dfrac{mn^d}{S^d})\)

简单列一个方程,当 \(S=n^{\frac{d-1}{d}}\) 的时候时间复杂度取到 \(\mathcal{Θ}(dmn^{\frac{d-1}{d}})\),几乎已经是最优解了。

那么到此我们就算解决了莫队的高维扩展问题。


回到开始的例题,此时各位是否有一个另外的想法冒出来呢?

没错,接下来我们要说一个时间复杂度为 \(O(mn^{\frac{3}{4}})\) 的算法。

我们把 \(l,r,a,b\) 设成四个维度,显然我们可以进行 \(O(1)\) 的推导。

记录数组 \(cnt1[i]\) 表示 \(i\)\([l,r]\) 中的出现次数,\(cnt2[i]\) 表示 \(i\) 是否在 \([l,r]\) 中出现。

显然在推导 \(l,r\) 的时候可以顺手 \(O(1)\) 修改 \(cnt1,cnt2\)

在推导 \(a,b\) 的时候,实际上我们需要的两个答案即为 \([cnt1[a],cnt1[b]]\) 的区间和以及 \([cnt2[a],cnt2[b]]\) 的区间和。

区间和好说啊,大家肯定都知道怎么维护啦~

因此我们其实将原问题建模成了对于 \(cnt1,cnt2\) 的修改和查询区间和问题……?

总之,按照上述的思路,我们不难写出四维莫队的代码。

CODE

#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=100010,MX=100010;
struct query{
	int id,l,r,a,b;
}q[M];
int len;
inline bool cmp(query a,query b)
{
	if(a.l/len==b.l/len&&a.r/len==b.r/len&&a.a/len==b.a/len)return a.b<b.b;
	if(a.l/len==b.l/len&&a.r/len==b.r/len)return a.a<b.a;
	if(a.l/len==b.l/len)return a.r<b.r;
	return a.l<b.l;
}//分块放进排序里面...
int a[N],cnt1[MX],cnt2[MX],now1,now2;
int ans1[M],ans2[M];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",a+i);
	for(int i=1;i<=m;i++)scanf("%d%d%d%d",&q[i].l,&q[i].r,&q[i].a,&q[i].b),q[i].id=i;
	len=pow(n,(double)3/4);
	sort(q+1,q+m+1,cmp);
	int L=1,R=0,A=1,B=0;
	for(int i=1;i<=m;i++)
	{
		/*
		区间转换?
		这里其实应该先变化AB				
		对于LR的变化就是板子
		(在变化的时候顺便要维护一下now)
        这里其实有一个问题:为什么一定要先维护AB呢?
        当然是留给读者思考咯^_^
		*/
		while(A>q[i].a)now1+=cnt1[--A],now2+=cnt2[A];
		while(B<q[i].b)now1+=cnt1[++B],now2+=cnt2[B];
		while(A<q[i].a)now2-=cnt2[A],now1-=cnt1[A++];
		while(B>q[i].b)now2-=cnt2[B],now1-=cnt1[B--];
		while(L>q[i].l)
		{
			if(!cnt1[a[--L]])
			{
				cnt2[a[L]]++;
				if(a[L]>=A&&a[L]<=B)now2++;
			}
			cnt1[a[L]]++;
			if(a[L]>=A&&a[L]<=B)now1++;
		}
		while(R<q[i].r)
		{
			if(!cnt1[a[++R]])
			{
				cnt2[a[R]]++;
				if(a[R]>=A&&a[R]<=B)now2++;
			}
			cnt1[a[R]]++;
			if(a[R]>=A&&a[R]<=B)now1++;
		}
		while(L<q[i].l)
		{
			if(a[L]>=A&&a[L]<=B)now1--;
			cnt1[a[L]]--;
			if(!cnt1[a[L]])
			{
				cnt2[a[L]]--;
				if(a[L]>=A&&a[L]<=B)now2--;
			}
			L++;
		}
		while(R>q[i].r)
		{
			if(a[R]>=A&&a[R]<=B)now1--;
			cnt1[a[R]]--;
			if(!cnt1[a[R]])
			{
				cnt2[a[R]]--;
				if(a[R]>=A&&a[R]<=B)now2--;
			}
			R--;
		}
		ans1[q[i].id]=now1;
		ans2[q[i].id]=now2;
	}
	for(int i=1;i<=m;i++)printf("%d %d\n",ans1[i],ans2[i]);
	return 0;
}

这份代码在校内OJ拿到了 36pts 的高分!

我们简单分析一下:对于原题目的数据而言,时间复杂度约为 \(\mathcal{Θ}(4mn^{\frac{3}{4}})=\mathcal{Θ}(4\times 10^5\times 10^{\frac{15}{4}})\approx\mathcal{Θ}(4\times 10^5\times 6000)=\mathcal{Θ}(24\times 10^8)\)

所以是被卡常了,悲。

不过令人感到欣慰的是,luogu给了3s时限能过(2.33s)。


最后说一句,高维莫队非常慢,大部分时候只能拿部分分,但是一般来说,拿到的部分分是很足的。

以高维莫队作为正解的题目几乎没有,所以学了也就学了,不用去练的。

posted @ 2024-02-02 16:28  小山云盘  阅读(146)  评论(2)    收藏  举报