【学习笔记】莫队算法
零.
这是一篇特别草率的学习笔记。
一.关于莫队
一种离线的关于区间操作的算法。
一般莫队会和以下知识点一起使用:
1.分块思想。
2.离散化。
3.树链剖分/倍增,用于应付树上的(然而本蒟蒻写这篇博客时还不会,后续补充)
二.算法实现
1.SPOJ 3267 D-Query
暴力做法:记录每个数字出现的次数,每个询问统计出现区间内次数不为0的数字个数。
复杂度:O(n2)
显然过不去。
考虑优化方法。每次遇到新数那么它出现次数一定是0,遇到旧数如果后面没有了那么它出现次数一定是1。
用双指针扫区间?
对于每个询问区间用指针移动来代替枚举即可实现上面的优化。
如果区间特别多怎么办?
考虑分块,按照块的下标和每个询问的右端点排序,那么就避免了双指针扫过去又扫回来的情况。
这就是莫队的基本操作了。
总复杂度:O(nlogn)+O(n√n)+O(n√n)=O(n√n)
同时这是一道莫队裸题。本题AC代码:(莫队模板)
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct Query 4 { 5 int l,r,num,pos; 6 bool operator < (const Query &k) const 7 { 8 if(pos==k.pos) return r<k.r; 9 return pos<k.pos; 10 } 11 }q[200020]; 12 int n,m,ans,a[200020],res[200020],cnt[1000020]; 13 int main() 14 { 15 scanf("%d",&n); 16 int size=sqrt(n); 17 for(int i=1;i<=n;i++) 18 { 19 scanf("%d",&a[i]); 20 } 21 scanf("%d",&m); 22 for(int i=1;i<=m;i++) 23 { 24 scanf("%d%d",&q[i].l,&q[i].r); 25 q[i].num=i; 26 q[i].pos=(q[i].l-1)/size+1; 27 } 28 sort(q+1,q+m+1); 29 int l=1,r=0; 30 for(int i=1;i<=m;i++) 31 { 32 while(q[i].l<l) 33 { 34 l--; 35 if(cnt[a[l]]==0) 36 { 37 ans++; 38 } 39 cnt[a[l]]++; 40 } 41 while(q[i].r>r) 42 { 43 r++; 44 if(cnt[a[r]]==0) 45 { 46 ans++; 47 } 48 cnt[a[r]]++; 49 } 50 while(q[i].l>l) 51 { 52 cnt[a[l]]--; 53 if(cnt[a[l]]==0) 54 { 55 ans--; 56 } 57 l++; 58 } 59 while(q[i].r<r) 60 { 61 cnt[a[r]]--; 62 if(cnt[a[r]]==0) 63 { 64 ans--; 65 } 66 r--; 67 } 68 res[q[i].num]=ans; 69 } 70 for(int i=1;i<=m;i++) 71 { 72 printf("%d\n",res[i]); 73 } 74 return 0; 75 }
2.洛谷 P2709 小B的询问
又一道莫队裸题。一个元素进入区间的贡献res=(cnt+1)2-cnt2=2*cnt+1。
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct Query 4 { 5 int l,r,num,pos; 6 bool operator < (const Query &k) const 7 { 8 if(pos==k.pos) return r<k.r; 9 return pos<k.pos; 10 } 11 }q[100001]; 12 int n,m,k,ans,a[100001],cnt[100001],res[100001]; 13 int main() 14 { 15 scanf("%d%d%d",&n,&m,&k); 16 int size=sqrt(n); 17 for(int i=1;i<=n;i++) 18 { 19 scanf("%d",&a[i]); 20 } 21 for(int i=1;i<=m;i++) 22 { 23 scanf("%d%d",&q[i].l,&q[i].r); 24 q[i].num=i; 25 q[i].pos=(q[i].l-1)/size+1; 26 } 27 sort(q+1,q+m+1); 28 int l=1,r=0; 29 for(int i=1;i<=m;i++) 30 { 31 while(q[i].l<l) 32 { 33 l--; 34 cnt[a[l]]++; 35 ans+=2*cnt[a[l]]-1; 36 } 37 while(q[i].r>r) 38 { 39 r++; 40 cnt[a[r]]++; 41 ans+=2*cnt[a[r]]-1; 42 } 43 while(q[i].l>l) 44 { 45 cnt[a[l]]--; 46 ans-=2*cnt[a[l]]+1; 47 l++; 48 } 49 while(q[i].r<r) 50 { 51 cnt[a[r]]--; 52 ans-=2*cnt[a[r]]+1; 53 r--; 54 } 55 res[q[i].num]=ans; 56 } 57 for(int i=1;i<=m;i++) 58 { 59 printf("%d\n",res[i]); 60 } 61 return 0; 62 }
3.洛谷 P1494 小Z的袜子
一段区间[l,r]的两两配对数量是(r-l+1)*(r-l+1-1)/2。
设一段区间里颜色为i的袜子数量是k,则抽到两只袜子i的概率就是k*(k-1)/(r-l+1)*(r-l+1-1)。
最后对概率分组求和就行了。(可能还需要再化简一次吧,暴力拆括号就行了)
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct Query 4 { 5 long long l,r,num,pos; 6 bool operator < (const Query &k) const 7 { 8 if(pos==k.pos) return r<k.r; 9 return pos<k.pos; 10 } 11 }q[50001]; 12 struct Reply 13 { 14 long long x,y; 15 }ans[50001]; 16 long long n,m,a[50001]; 17 long long cnt[50001],res; 18 long long GCD(long long x,long long y) 19 { 20 return y==0?x:GCD(y,x%y); 21 } 22 int main() 23 { 24 scanf("%lld%lld",&n,&m); 25 int size=sqrt(n); 26 for(int i=1;i<=n;i++) 27 { 28 scanf("%lld",&a[i]); 29 } 30 for(int i=1;i<=m;i++) 31 { 32 scanf("%lld%lld",&q[i].l,&q[i].r); 33 q[i].num=i; 34 q[i].pos=(q[i].l-1)/size+1; 35 } 36 sort(q+1,q+1+m); 37 int l=1,r=0; 38 for(int i=1;i<=m;i++) 39 { 40 while(q[i].l<l) 41 { 42 l--; 43 res-=cnt[a[l]]*cnt[a[l]]; 44 cnt[a[l]]++; 45 res+=cnt[a[l]]*cnt[a[l]]; 46 } 47 while(q[i].r>r) 48 { 49 r++; 50 res-=cnt[a[r]]*cnt[a[r]]; 51 cnt[a[r]]++; 52 res+=cnt[a[r]]*cnt[a[r]]; 53 } 54 while(q[i].l>l) 55 { 56 res-=cnt[a[l]]*cnt[a[l]]; 57 cnt[a[l]]--; 58 res+=cnt[a[l]]*cnt[a[l]]; 59 l++; 60 } 61 while(q[i].r<r) 62 { 63 res-=cnt[a[r]]*cnt[a[r]]; 64 cnt[a[r]]--; 65 res+=cnt[a[r]]*cnt[a[r]]; 66 r--; 67 } 68 if(q[i].l==q[i].r) 69 { 70 ans[q[i].num].x=0; 71 ans[q[i].num].y=1; 72 continue; 73 } 74 ans[q[i].num].x=res-(q[i].r-q[i].l+1); 75 ans[q[i].num].y=(q[i].r-q[i].l+1)*(q[i].r-q[i].l); 76 long long G=GCD(ans[q[i].num].x,ans[q[i].num].y); 77 ans[q[i].num].x/=G; 78 ans[q[i].num].y/=G; 79 } 80 for(int i=1;i<=m;i++) 81 { 82 printf("%lld/%lld\n",ans[i].x,ans[i].y); 83 } 84 return 0; 85 }
4.洛谷 P3709 大爷的字符串题
首先那个鬼集合是一定不会空掉的。
然后我们需要人品最高,那么我们就需要清空集合的次数最少了。
但是题目说的是不小于,所以我们不能按照从小到大的顺序无脑往里面放。
考虑一下放的顺序。
如果我们把区间内出现次数最多的数一下无脑放进去会减掉cnt-1的人品。
但是如果我们把比它小的重复数字穿插进去的话减掉的人品还是cnt-1啊。
所以我们只要这样放的话就是统计众数出现了几次啊。
好了 既然知道题意了,那就打莫队吧。
1 #include<bits/stdc++.h> 2 using namespace std; 3 struct Query 4 { 5 int l,r,num,pos; 6 bool operator < (const Query &k) const 7 { 8 if(pos==k.pos) return r<k.r; 9 return pos<k.pos; 10 } 11 }q[200001]; 12 int n,m,a[200001],P[200001]; 13 int tot[200001],cnt[200001],maxx,Res[200001]; 14 int main() 15 { 16 scanf("%d%d",&n,&m); 17 int size=sqrt(n); 18 for(int i=1;i<=n;i++) 19 { 20 scanf("%d",&a[i]); 21 P[i]=a[i]; 22 } 23 sort(P+1,P+n+1); 24 int len=unique(P+1,P+n+1)-P-1; 25 for(int i=1;i<=n;i++) 26 { 27 a[i]=lower_bound(P+1,P+len+1,a[i])-P; 28 } 29 for(int i=1;i<=m;i++) 30 { 31 scanf("%d%d",&q[i].l,&q[i].r); 32 q[i].num=i; 33 q[i].pos=(q[i].l-1)/size+1; 34 } 35 sort(q+1,q+m+1); 36 int l=1,r=0; 37 for(int i=1;i<=m;i++) 38 { 39 while(q[i].l<l) 40 { 41 l--; 42 tot[cnt[a[l]]]--; 43 tot[++cnt[a[l]]]++; 44 maxx=max(maxx,cnt[a[l]]); 45 } 46 while(q[i].r>r) 47 { 48 r++; 49 tot[cnt[a[r]]]--; 50 tot[++cnt[a[r]]]++; 51 maxx=max(maxx,cnt[a[r]]); 52 } 53 while(q[i].l>l) 54 { 55 tot[cnt[a[l]]]--; 56 if(cnt[a[l]]==maxx && !tot[cnt[a[l]]]) maxx--; 57 tot[--cnt[a[l]]]++; 58 l++; 59 } 60 while(q[i].r<r) 61 { 62 tot[cnt[a[r]]]--; 63 if(cnt[a[r]]==maxx && !tot[cnt[a[r]]]) maxx--; 64 tot[--cnt[a[r]]]++; 65 r--; 66 } 67 Res[q[i].num]=maxx; 68 } 69 for(int i=1;i<=m;i++) 70 { 71 printf("%d\n",-Res[i]); 72 } 73 return 0; 74 }
三.带修改的莫队
待补充~