莫队算法学习笔记

从我的$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 }
代码戳这里
posted @ 2019-10-20 19:35  小叽居biubiu  阅读(132)  评论(0编辑  收藏  举报