【学习笔记】莫队算法

零.

  这是一篇特别草率的学习笔记。

一.关于莫队

  一种离线的关于区间操作的算法。

  一般莫队会和以下知识点一起使用:

  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 }
View Code

  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 }
View Code

  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 }
View Code

  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 }
View Code

三.带修改的莫队

  待补充~

posted @ 2019-09-18 19:16  RakanX  阅读(201)  评论(0编辑  收藏  举报