分块、莫队算法

版权声明:本文为博主原创文章,未经博主允许必须转载。 https://blog.csdn.net/C20181220_xiang_m_y/article/details/78882655

首先,是利用分块的思想处理区间问题

比如这个:(D-query点击打开链接

Given a sequence of n numbers a1, a2, ..., an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, ..., aj.

Input

  • Line 1: n (1 ≤ n ≤ 30000).
  • Line 2: n numbers a1, a2, ..., an (1 ≤ ai ≤ 106).
  • Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.
  • In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).

Output

  • For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, ..., aj in a single line.

Example

Input
5
1 1 2 1 3
3
1 5
2 4
3 5

Output
3
2
3
题目大意是要求一段区间内不同元素的个数

    首先,暴力分析,n*m,TLE

    然后,考虑优化,如何尽可能的利用已经求过的值,这是莫队算法的核心,也是近乎所有优化的核心

    先考虑这样一个问题:如果我们已经知道了区间[L,R]之间的内容(此处即为数字的个数,记为cnt[number]==个数),并且已经知道[L,R]之间不同元素的个数(此处记为sum)。

    那么当我们要求[L-1,R]或是[L,R+1]时,就可以以O(1)的时间得到更新的cnt,并通过cnt的变化(此处表现为cnt[num]刚好加到一次或刚好减为0)来得到新的sum值

相信这个思想大家曾经都想过,但如何使L和R移动的次数尽可能少而覆盖所有的询问,是莫队所要解决的。

自然而然的,我们想到按照L从小到大将询问排序,但当L变化时,R仍然可能从L取到n,时间仍很大。

于是我们将L分块,(从现在起,记l为数组下标,L为该下标所在块,r和R同理)

我们将询问以L为第一关键字,R为第二关键字排序,

那么,询问的区间就会变成这样:

[L,R],[L,R],[L,R+1],[L,R+k],[L,n],

[L+1,R+2],[L+1,R+2],[L+1,R+k],

[L+k,R+p].......

此时再启用暴力修改,可以发现:

在同一块内,l和r的波动范围(记每块长度为k)就是k,时间复杂度降为了((n/k)*(n/k)*k),取k=sqrt(n)时为O(n*sqrt(n)).

通过巧妙的阈值,莫队通过L的波动平衡了R变化的O(n*n)与L变化的O(n),是指总复杂度变为O(n*sqrt(n)).


代码如下:

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
int n,m,k,a[30005],cnt[1000005],x,y,sum,ans[200005];//注意cnt的大小,不然容易RE
struct node
{
	int l,r,L,R,id;
	bool operator < (const node &p)const
	{
		if(L==p.L) return R<p.R;
		else return L<p.L;
	}
}q[200005];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	k=sqrt(n);scanf("%d",&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i,q[i].L=q[i].l/k,q[i].R=q[i].r/k;
	sort(q+1,q+1+m);//离线处理询问
	x=1,y=sum=0;//设置初始区间
	for(int i=1;i<=m;i++)
	{
		while(x>q[i].l) sum+=(++cnt[a[--x]]==1);//修改范围,拓展或收缩
		while(y<q[i].r) sum+=(++cnt[a[++y]]==1);
		while(x<q[i].l) sum-=(--cnt[a[x++]]==0);
		while(y>q[i].r) sum-=(--cnt[a[y--]]==0);
		ans[q[i].id]=sum;
	}
	for(int i=1;i<=m;i++)
		printf("%d\n",ans[i]);
}


另外,莫队的变式通常会在sum值的统计上作文章。

比如Powerful array 点击打开链接

sum的求法变成了这样:

while(x>q[i].l) sum+=(2*cnt[a[x-1]]+1)*a[x-1],cnt[a[--x]]++;
while(y<q[i].r) sum+=(2*cnt[a[y+1]]+1)*a[y+1],cnt[a[++y]]++;
while(x<q[i].l) sum-=(2*cnt[a[x]]-1)*a[x],cnt[a[x++]]--;
while(y>q[i].r) sum-=(2*cnt[a[y]]-1)*a[y],cnt[a[y--]]--;

还要注意莫队的一个坑:x,y的拓展顺序,先拓展、在收缩,不然可能会出现x调整时到了还未来得及调整的y的右边,导致cnt为负数,在某些情况下并不影响答案(如上面两题),但某些情况则会(如下):

while(y<q[i].r) sum=sum*inv[++cnt[a[++y]]]%mod;//attention!!
while(x>q[i].l) sum=sum*inv[++cnt[a[--x]]]%mod;//first go out.avoid -number.
while(x<q[i].l) sum=sum*cnt[a[x++]]--%mod;
while(y>q[i].r) sum=sum*cnt[a[y--]]--%mod;
此处若sum的求法如上,则cnt为负时数组访问便会越界。( NPY and girls 点击打开链接

还有,莫队的变式有时会涉及到前缀数组,要把原数组进行一些处理:

XOR and Favorite Number 点击打开链接

该题需要求的是一个区间异或的“和”,注意,这种前缀题,区间的左端点要往前取一位(相减才能把整个区间取完)

修改大致如下:

scanf("%d",&a[i]),a[i]^=a[i-1];
scanf("%d%d",&q[i].l,&q[i].r),q[i].l--,q[i].id=i,q[i].L=q[i].l/k,q[i].R=q[i].r/k;
while(x>q[i].l) sum+=cnt[p^a[x-1]],cnt[a[--x]]++;
while(y<q[i].r) sum+=cnt[p^a[y+1]],cnt[a[++y]]++;
while(x<q[i].l) cnt[a[x]]--,sum-=cnt[p^a[x++]];
while(y>q[i].r) cnt[a[y]]--,sum-=cnt[p^a[y--]];



最后,是莫队的修改操作。将第几次修改作为第三关键字排序,记录每个修改操作施加的位置、以及修改前和修改后的值,进行暴力修改和还原,记区间长为k,则当L和R一定时,t从1取到n,O((n/k)^2*n),R波动n*k,L波动n*k,当k取n的三分之二次方时总复杂度最小(通常可以直接令k=100~300)

PS:注意两个修改操作如果施加在同一点,则第二次操作的原值应为第一次修改后的值(所以最好开两个数组存原序列)。

数颜色

  点击打开链接

struct node
{
	int l,r,L,R,t,id;
	bool operator < (const node &p)const{
		if(L==p.L)
		{
			if(R==p.R) return t<p.t;
			else return R<p.R;
		}	
		else return L<p.L;
	}
}q[maxn];
if(c[0]=='Q')
		{
			int id=i-tim;
			scanf("%d%d",&q[id].l,&q[id].r);
			q[id].id=id;q[id].t=tim;
			q[id].L=q[id].l/k,q[id].R=q[id].r/k;
		}
else 
		{
			tim++;
			scanf("%d%d",&turn[tim].pos,&turn[tim].cl2);
			turn[tim].cl1=b[turn[tim].pos];
			b[turn[tim].pos]=turn[tim].cl2;
		}
while(t<q[i].t)
		{
			t++;
			a[turn[t].pos]=turn[t].cl2;
			if(x<=turn[t].pos&&turn[t].pos<=y)
			{
				sum-=(--cnt[turn[t].cl1]==0);
				sum+=(++cnt[turn[t].cl2]==1);
			}
		}
while(t>q[i].t)
		{
			a[turn[t].pos]=turn[t].cl1;
			if(x<=turn[t].pos&&turn[t].pos<=y)
			{
				sum-=(--cnt[turn[t].cl2]==0);
				sum+=(++cnt[turn[t].cl1]==1);
			}
			t--;
		}






猜你喜欢

转载自blog.csdn.net/C20181220_xiang_m_y/article/details/78882655