谈谈基础莫队

莫队从某种意义上来说就是一种 “优雅” 的暴力;

莫队一般用于多次区间查询,举个栗子:

在区间查询中存在两个指针分别表示某一个区间的左右边界,j 和 i 的值代表区间 [ j ,  i ] ,而莫队的思想则是当我需要去查询某一个区间时,通过暴力移动 j , i 指针的方式去从上一个区间移动到当前我们需要查询的区间。

 

    ——————————————————————————————————

    1  2  3  5  6  7  8  9  10  11  12  13  14  

                   j                i 

此时 j = 7, i = 11 , 而如果说我们需求去查询区间 [ 8 , 10 ] 我们则需要将 j + 1,i - 1 那么操作之后,j = 8, i = 10 ,代表的是区间 [ 8,  10 ] ;

**目前查询的区间是通过上一个查询的区间暴力改变两个边界指针而得到的;这里的 j 指针为左指针,i 指针为右指针;

其实莫队的方式就是将所以需要查询的区间都以上面所说的方式从头到尾遍历,欸!!这里就有点不对劲了,这样不会超时吗??

为了不超时,我们在遍历之前还有一步操作,而这步操作正是莫队的灵魂!!!而我们需要去考虑,怎样的操作才能更好的优化这个暴力查询的过程呢。我们知道,暴力查询的复杂度是与这里的 j 和 i 指针移动的步数是呈正相关的,所以我们的优化可以从 j 和 i 指针上面下功夫,即我们需要去让 j 和 i 指针在遍历的过程中让遍历的步数更多的减少。

这里我们需要去用到分块的思想(不知道分块也不要紧,只需要去明白它的思想就可以辣),这里我们只通过分块思想考虑 j 指针的情况,而如果去优化 j 对应的 i 指针呢,我们来思考,举个栗子:

  5 -> 3 -> 6 -> 1 

这里我们需要去遍历上面的 4 个数,但是这种方式肯定不是最优的,那么如何才能最优呢,这里将上面 4 个数排个序我们发现最优的就出来了:

  1 -> 3 -> 5 -> 6 

排序之前的步数为 10 而排序之后步数就成了 5 ,欸我们发现就简简单单排个序竟然减少了不少的步数;下面是排序的代码:

 1 struct node
 2 {
 3     int id, l, r;//其中 id 表示第 id 个查询的操作,l 和 r 分别表示的是左右边界
 4 } q[M];
 5 int get(int x)
 6 //返回 x 所属的块
 7 {
 8     return x / len;
 9 }
10 bool cmp(node a, node b)
11 {
12     int i = get(a.l), j = get(b.l);
13     if (i != j)
14         return i < j;
15     //当不在同一个块的时候,按其右指针的大小顺序排序
16     return a.r < b.r;
17     //当在同一个块的时候,按其右指针的大小排序
18 }

这里之所以用分块的思想去处理 j 指针,是因为我们并不能将所有的查询区间都直接按一种方式排序,而这里处理 j 指针和 i 指针可以这样理解:(个人理解)

  我们将一些隔的不远的左边界放在一起,然后在这些放在一起的左边界的右边界去排序让 i 指针遍历的时候步数更少,而其实这里将相隔不远的左边界放在一起的时候,左指针遍历的时候的步数也是一种优化。而为了让左指针更少的左右跑动减少其重复遍历,所以将不在同一分块的左边界按从小到大的方式排序。

这里分块的长度为 n / m ^ 0.5 ,这里的 n 是所有元素的个数,m 是查询的次数(这里其实直接记住就可以了,还挺好记的)

**其实这里还有一点点的优化,我们知道在所有分块里面的右指针是排好序的,但如果每一个分块的排序方式都是相同的话,那每一次遍历到一个新的分块时,右指针的位置需要从它上一个位置先回到排序的最小的位置然后从最小的位置往最大的位置遍历,所以这里我们可以改变一下每一个分块的排序,当 i 指针从小的位置跑到大的位置后,如果这时结束了当前的分块的遍历进入下一个分块时,我们去找新的分块里面最大的位置,然后从最大的位置往最小的位置遍历,此时判断一下奇偶性就可以了:

 1 bool cmp(node a, node b)
 2 {
 3     int i = get(a.l), j = get(b.l);
 4     if (i != j)
 5         return i < j;
 6     //当不在同一个块的时候,按其右指针的大小顺序排序
 7     if(i % 2 == 1)
 8     return a.r < b.r;
 9     if(i % 2 == 0)  
10     return a.r > b.r;
11     //当在同一个块的时候,按其右指针的大小排序
12 }

查询顺序排序结束后我们按照最后的顺序查询就可以了,下面是我的完整代码:

 1 #include <iostream>
 2 #include <cstdio>
 3 #include <algorithm>
 4 #include <vector>
 5 #include <cmath>
 6 #include <cstring>
 7 #include <queue>
 8 #include <map>
 9 #include <unordered_map>
10 #include <set>
11 #include <stack>
12 #define ll long long
13 #define ull unsigned long long
14 #define ci const int
15 #define PLL pair<ll, ll>
16 #define PII pair<int, int>
17 #define PSI pair<string, int>
18 #define MAX1 0x3f3f3f3f
19 #define lowbit(x) x &(-x)
20 using namespace std;
21 ci N = 5e4 + 7, M = 2e5 + 7, S = 1e6 + 7;
22 int n, m, len;
23 int w[N], ans[M], cnt[S];
24 struct node
25 {
26     int id, l, r;
27 } q[M];
28 int get(int x)
29 //返回 x 所属的块
30 {
31     return x / len;
32 }
33 bool cmp(node a, node b)
34 {
35     int i = get(a.l), j = get(b.l);
36     if (i != j)
37         return i < j;
38     //当不在同一个块的时候,按其右指针的大小顺序排序
39     if(i % 2 == 1)
40     return a.r < b.r;
41     if(i % 2 == 0)  
42     return a.r > b.r;
43     //当在同一个块的时候,按其右指针的大小排序
44 }
45 void add(int x, int &res)
46 //添加操作
47 {
48     if(!cnt[x])
49         res++;
50     cnt[x]++;
51 }
52 void del(int x, int &res)
53 //删除操作
54 {
55     cnt[x]--;
56     if(!cnt[x])
57         res--;
58 }
59 int main()
60 {
61     scanf("%d", &n);
62     for(int i = 1; i <= n; i++)
63         scanf("%d", &w[i]);
64     scanf("%d", &m);
65     len = max(1, n / (int)sqrt(m));
66     for(int i = 0; i < m; i++)
67     {
68         int l, r;
69         scanf("%d%d", &l, &r);
70         q[i] = {i, l, r};
71     }
72     sort(q, q + m, cmp);
73     for(int k = 0, i = 0, j = 1, res = 0; k < m; k++)
74     {
75         int id = q[k].id, l = q[k].l, r = q[k].r;
76         while(i < r)
77             add(w[++i], res);
78         while(i > r)
79             del(w[i--], res);
80         while(j < l)
81             del(w[j++], res);
82         while(j > l)
83             add(w[--j], res);
84         ans[id] = res;
85     }
86     for(int i = 0; i < m; i++)
87         printf("%d\n", ans[i]);
88     return 0;
89 }

这里求的 res 是每个区间不同种数字的种数,理解基础莫队的就好了哈哈。

posted @ 2022-07-15 14:16  aYi_7  阅读(55)  评论(0)    收藏  举报