莫队算法学习笔记
从我的$Luogu$博客上搬运过来的$QwQ$,很久以前写的了,趁着这次考试考到了复习一下
莫队分为三种:普通莫队,树形莫队,带修改莫队。
【例 1】小Z的袜子 题目传送门
题目大意:进行区间询问$[l,r]$,输出在该区间内随机抽两次抽到相同颜色袜子的概率。
分析:首先考虑对于一个长度为$n$的区间内的答案如何求解。题目要求答案用最简分数表示:那么分母就是$n*(n-1)($表示两两袜子之间的随机组合$)$,分子是将该区间内每种颜色$i$出现次数的平方的累加求出的和。
莫队思路:离线情况下对所有的询问进行一个$sort$排序,然后两个指针$l,r$不断以“暴力”的方式在区间内跳来跳去$($所以到底应该怎么跳嘞?这不是相当于没讲吗? $)$,最终输出答案。
思想基础:$($关于如何“跳来跳去”$)$
$<1>$两个询问之间的状态跳转。假设当前完成询问的区间为$[a,b]$,下一个询问的区间为$[p,q]$,现在保存$[a,b]$区间内的每个颜色出现次数$sum$和答案$ans$,通过移动指针来转移答案。
$<2>$考虑指针向左或向右移动一个单位,我们要付出多大的代价才能维护$sum$和$ans($即让$sum$和$ans$保存的是当前$[l,r]$的正确信息,我终于知道维护的真实含义了$)$。
$<3>$假设指针移动一个单位后多出来$(or$减少$)$的袜子颜色为$a$,那么就更改$sum[a](++or- -)$,同时维护$ans$的值,分母由$n^2$变为$(n-1)^2\ or\ (n+1)^2$,分子要减去原来答案的贡献$($即原$sum[a]^2)$,再加上现在的答案贡献$($即现$sum[a]^2)$。
优化:由于以上方法最坏情况时间复杂度为$O(n^2)$,所以要用排序优化一下。
分块!用$pos$数组存储每个$l$在哪一块。对询问进行排序,如果$pos[l]$相同,那么就以$r$为关键字排序;否则以$l$为关键字排序。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=50003; 4 struct Modui{//开一个结构体存储每个询问 5 int l,r,id; 6 long long A,B; 7 }q[N]; 8 long long gcd(long long a,long long b){//求最大公约数,用于约分 9 long long mid=b;//蒟蒻的我只会用辗转相除法QAQ 10 b=a%b; 11 if(b==0) return mid; 12 else return gcd(mid,b); 13 } 14 int n,m,color[N],t,pos[N]; 15 long long sum[N],ans;//ans存储当前区间抽中两只同色袜子的方案总数 16 bool cmp1(Modui a,Modui b){ 17 return pos[a.l]==pos[b.l]?a.r<b.r:a.l<b.l;//排序,判断关键字 18 } 19 bool cmp2(Modui a,Modui b){//输出前按输入的顺序排序 20 return a.id<b.id; 21 } 22 void change(int x,int add){//转移答案 23 ans-=sum[color[x]]*(sum[color[x]]-1); 24 sum[color[x]]+=add; 25 ans+=sum[color[x]]*(sum[color[x]]-1); 26 return; 27 } 28 int main(){ 29 scanf("%d%d",&n,&m); 30 t=sqrt(n);//t存储分块的数量 31 for(int i=1;i<=n;i++) 32 scanf("%d",&color[i]),pos[i]=i/t+1; 33 for(int i=1;i<=m;i++) 34 scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i; 35 sort(q+1,q+m+1,cmp1); 36 int l=1,r=0;//l,r两个指针存储当前的位置 37 for(int i=1;i<=m;i++){ 38 while(l<q[i].l) change(l,-1),l++; 39 while(l>q[i].l) change(l-1,1),l--; 40 while(r<q[i].r) change(r+1,1),r++; 41 while(r>q[i].r) change(r,-1),r--;//把指针移动到当前询问的区间 42 if(q[i].l==q[i].r) {q[i].A=0;q[i].B=1;continue;} 43 //只有一只袜子的情况要特判 44 q[i].A=ans;//A代表分子,等于方案总数 45 q[i].B=1LL*(q[i].r-q[i].l+1)*(q[i].r-q[i].l); 46 //B代表分母,等于区间内所有袜子组合的方案总数 47 long long GCD=gcd(q[i].A,q[i].B); 48 q[i].A/=GCD;q[i].B/=GCD;//约分 49 } 50 sort(q+1,q+m+1,cmp2); 51 for(int i=1;i<=m;i++)//排序后按序输出 52 printf("%lld/%lld\n",q[i].A,q[i].B); 53 return 0; 54 }