莫队算法
前言
当初我问ZigZagK什么是莫队算法时,他给了我一个神犇的眼神,飘了一句:“就是一个高级的暴力。”当时我彻底凌乱,后来翻了下他的Blog,发现……神犇都喜欢把话都说的很简洁……
实现
莫队算法,就是当你处理一个区间问题时,已经知道了[L,R]的答案,求[L’,R’],那么如果可以通过较快的复杂度得到[L+1,R],[L-1,R],[L,R+1],[L,R-1]的答案,就可以指针移动得到[L’,R’]。(所以确实有点暴力)
但是如果数据是[1,1]和[n,n]交替,呵呵,卡死你,所以为了优化,所以莫队用分块的思想进行了优化。
将Q个询问序列按照L分块,块内按R从小到大排序。这样的话利用复杂度是……
计算次数=L移动次数+R移动次数
L移动次数:
1.相同块中,i到i+1最多移动 n−−√ n 次,总共Q个询问,移动次数为Q* n−−√ n 。
2.不同块中,i到i+1最多移动 n−−√ n 次,总共 n−−√ n 个块,移动次数为n。
R移动次数:
1.相同块中,总计移动最多n次,总共 n−−√ n 个块,移动次数为n n−−√ n 。
2.不同块中,i到i+1最多移动n次,总共 n−−√ n 个块,移动次数为n* n−−√ n 。
——以上证明皆由ZZK提供
所以总复杂度是 O((n+q)∗n−−√) O ( ( n + q ) ∗ n ) 。
模板
我知道我之前用树状数组写过这题,但是这道题其实是莫队裸体,所以又写了一遍……
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,q,S,c[50005],ha[1000005],ans[200005],sum;
struct data{
int L,R,num,id;
bool operator < (const data b)const{
return num<b.num||(num==b.num&&R<b.R);
}
}a[200005];
inline void readi(int &x){
x=0; char ch=getchar();
while ('0'>ch||ch>'9') ch=getchar();
while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
}
void _change(int x,int tem){
if (!ha[c[x]]) sum++;
ha[c[x]]+=tem;
if (!ha[c[x]]) sum--;
}
int main()
{
freopen("necklace.in","r",stdin);
freopen("necklace0.out","w",stdout);
readi(n); for (int i=1;i<=n;i++) readi(c[i]);
S=sqrt(n); readi(q);
for (int i=1;i<=q;i++){
readi(a[i].L); readi(a[i].R);
a[i].num=(a[i].L-1)/S+1; a[i].id=i;
}
sort(a+1,a+q+1); sum=0;
memset(ha,0,sizeof(ha));
for (int i=a[1].L;i<=a[1].R;i++) {if (!ha[c[i]]) sum++; ha[c[i]]++;} ans[a[1].id]=sum;
for (int i=2;i<=q;i++){
int L=a[i-1].L,R=a[i-1].R;
while (L<a[i].L) _change(L++,-1);
while (L>a[i].L) _change(--L,1);
while (R<a[i].R) _change(++R,1);
while (R>a[i].R) _change(R--,-1);
ans[a[i].id]=sum;
}
for (int i=1;i<=q;i++) printf("%d\n",ans[i]);
return 0;
}