莫队的一道板子题,可以说是目前看到最简单的可以用莫队的题了。
题目大意:
现有数列A1,A2,⋯,AN,Q 个询问(Li,Ri),判断ALi,ALi+1,⋯,ARi 是否互不相同。(支持离线询问)
首先,我们显然可以看到暴力是一定会超时的,因为暴力的复杂度太大:O(qn)这个直接TLE没商量
我们说一个稍微慢一些但是也能轻松过这道题的算法:莫队(优雅的暴力)
莫队总体可以分为四句话:
1.将整个序列分为若干个块,每一块大小一般都会分为sqrt(n)。
2.对于整个序列进行排序,以左端点所在块的位置作为第一关键字,以右端点位置作为第二关键字,都做升序排序
3.暴力求出第一个块的答案
4.利用已知答案进行转移
可能大家不太明白第四条,在这里我来解释一下:
对于这道题来说,a的大小并不大,那么我们就可以开一个tot数组来记录区间有多少个相同的数x,再开一个sum记录有多少不同的数,每一次转移时分为两种情况:
1.加入一个新元素,我们只要看一下这个数vis数组是否为0,如果为零那么sum++
2.删去一个旧元素,还是要看是否为0,满足则sum--
不难发现这些转移都是O(1)的
那么关于莫队的时间复杂度:
1.对于右端点来说,由于在同一块内是递增的,每次最多转移n次,而有sqrt(n)个块,所以是O(nsqrt(n))
2.对于左端点来说,由于每一个块内最多转移n次,而有sqrt(n)个块,所以也是O(nsqrt(n))
根据加法原理,(这里省略了左端点跨块的复杂度证明,不过也是nsqrt(n)请自行推导)总复杂度为nsqrt(n) (这个是最大复杂度,实际比这个快得多。。。)
好的,莫队到这里就讲完了!
最后,附上本题代码:
1 #include<cstdio> 2 #include<cmath> 3 #include<algorithm> 4 #define maxn 100000 5 using namespace std; 6 7 int a[maxn+5]; 8 struct query 9 { 10 int l,r,id,to; 11 bool judge; 12 }; 13 int sum,appear[maxn+5],ans[maxn+5]; 14 query block[maxn+5]; 15 16 bool cmp(query x,query y) 17 { 18 if(x.id==y.id) return x.r<y.r; 19 return x.id<y.id; 20 } 21 void add(int x) 22 { 23 if(appear[a[x]]++==0) sum++; 24 } 25 void minus(int x) 26 { 27 if(--appear[a[x]]==0) sum--; 28 } 29 int main() 30 { 31 int n,q; 32 scanf("%d%d",&n,&q); 33 for(int i=1; i<=n; i++) scanf("%d",&a[i]); 34 int siz=sqrt(n); 35 for(int i=1; i<=q; i++) 36 { 37 int x,y; 38 scanf("%d%d",&x,&y); 39 block[i].l=x,block[i].r=y; 40 block[i].id=block[i].l/siz; 41 block[i].to=i; 42 } 43 sort(block+1,block+q+1,cmp); 44 for(int i=block[1].l; i<=block[1].r; i++) 45 { 46 if(appear[a[i]]==0) sum++; 47 appear[a[i]]++; 48 } 49 if(sum==block[1].r-block[1].l+1) ans[block[1].to]=1; 50 int Li=block[1].l,Ri=block[1].r; 51 for(int k=2; k<=q; k++) 52 { 53 while(block[k].l<Li) add(--Li); 54 while(block[k].l>Li) minus(Li++); 55 while(block[k].r>Ri) add(++Ri); 56 while(block[k].r<Ri) minus(Ri--); 57 if(sum==block[k].r-block[k].l+1) ans[block[k].to]=1; 58 } 59 for(int i=1; i<=q; i++) 60 { 61 if(ans[i]==1) printf("Yes "); 62 else printf("No "); 63 } 64 return 0; 65 }