首先,是利用分块的思想处理区间问题
比如这个:(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--;
}