莫队----十分适合处理离线的区间问题

所谓离线的意思是指:先读取全部的输入,经过算法的一系列操作,最后在按照读入的顺序输出答案
相对的,在线的意思是:读取一行输入,输出一行对应的答案
《分块》
由于莫队的核心思想是分块,所以先说一下分块这个思想:




1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 #include <cmath>
5 using namespace std;
6 typedef long long LL;
7 const int N = 1e5 + 10;
8 int n, m;
9 int a[N], sum[N], lazy[N], len;
//分块后,对于下标x,求出其所在的块号
10 int get(int x)
11 {
12 return x / len + 1;
13 }
14 void update(int l, int r, int k)
15 {
16 int q = get(l), p = get(r);
17 //同一块
18 if (q == p)
19 {
20 for (int i = l; i <= r; i++)
21 sum[q] += k, a[i] += k;
22 return;
23 }
24 //不同块
25 //先处理在相同块中的:
26 for (int i = q + 1; i <= p - 1; i++)
27 {
28 sum[i] += len * k;
29 // lazy[i]数组记录的是在整个第i块中受到的改变,
30 //如果没有lazy[]数组那么当整块受到改变时,就要遍历这一整块
//所以在下面求和过程中也要记得加上lazy[]数组
31 lazy[i] += k;
32 }
//右边离散的元素的改变
33 for (int i = l; get(i) == q; i++)
34 {
35 sum[q] += k;
36 a[i] += k;
37 }
//左边离散的元素的改变
38 for (int i = r; get(i) == p; i--)
39 {
40 sum[p] += k;
41 a[i] += k;
42 }
43 }
44 LL ask(int l, int r)
45 {
46 LL res = 0;
47 int q = get(l), p = get(r);
48 if (q == p)
49 for (int i = l; i <= r; i++)
50 res += (a[i] + lazy[q]);
51 else
52 {
53 //先处理同块:
54 for (int i = q + 1; i <= p - 1; i++)
55 res += sum[i];
56 for (int i = l; get(i) == q; i++)
57 res += (a[i] + lazy[q]);
58 for (int i = r; get(i) == p; i--)
59 res += (a[i] + lazy[p]);
60 }
61 return res;
62 }
63 int main()
64 {
65 cin >> n >> m;
66 //分组
67 len = sqrt(n);//获得每一块长度
68 for (int i = 1; i <= n; i++)
69 {
70 cin >> a[i];
//sum[i]代表在第i块的元素总和为sum[i];
71 sum[get(i)] += a[i];
72 }
73 int op, l, r, k;
74 while (m--)
75 {
76 cin >> op >> l >> r;
77 if (op == 1)
78 {
79 cin >> k;
80 update(l, r, k);
81 }
82 else
83 cout << ask(l, r) << endl;
84 }
85 return 0;
86 }

《普通莫队》
还是上面的题目:

知道上面的方法后,如果我已经知道了在区间[l,r]的值,为了求其他区域的值我可以如下操作:

说明:比如我知道了在区间[3,5]的和,求[2,5]的和,这时应该用Add(--3)==Add(2),同时l=2;
求[4,5]的和,这时应该Sub(3++)==Sub(3),同时l=4;
如果我们像上面那样操作,则:

所以我们要离线操作,首先读入全部的输入,然后对输入排序
加上我们的分块操作,有排序的原则:

1 #include <iostream>
2 #include <algorithm>
3 #include <cstring>
4 #include <cmath>
5 using namespace std;
6 const int N = 5 * 1e4 + 10;
7 int n, m, k;
8 int a[N], pos[N], ans[N]; // pos记录序号i在第几块,ans[i]记录第i次输入时的答案;
9 int res; //res全局维护的[l,r]之间的答案;
10 struct Q
11 {
12 int l, r, k;
13 } q[N];//记录输入的信息
14 void add(int x)
15 {
16 //不同题目不一样18 }
19 void sub(int x)
20 {
21 //不同题目不一样23 }
24 int main()
25 {
26 cin >> n >> m >> k;
27 //分块
28 int len = sqrt(n);
29 for (int i = 1; i <= n; i++)
30 {
31 cin >> a[i];
32 pos[i] = i / len + 1;
33 }
34
35 for (int i = 1; i <= m; i++)
36 {
37 cin >> q[i].l >> q[i].r;
//记录一下这个读入是第几个读入,好按读入顺序输出
38 q[i].k = i;
39 }
40 //将提问按照排序规则排序
41 sort(q + 1, q + m + 1, [](Q x, Q y)
42 { return pos[x.l] == pos[y.l] ? x.r < y.r : pos[x.l] < pos[y.l]; });
//这样初始化很有讲解
43 int l = 1, r = 0; //维护双指针,[l,r]这个范围的答案我们是知道的;
44 for (int i = 1; i <= m; i++)
45 {
46 while (q[i].l < l)
47 add(--l);
48 while (q[i].l > l)
49 sub(l++);
50 while (q[i].r > r)
51 add(++r);
52 while (q[i].r < r)
53 sub(r--);
54 ans[q[i].k] = res;
55 }
56 for (int i = 1; i <= m; i++)
57 cout << ans[i] << endl;
58 return 0;
59 }
对于这个题目:

开一个数组cnt[],cnt[i]表示数i在区间[l,r]上一共出现了cnt[i]次
1 void add(int x)
2 {
3 cnt[a[x]]++;
4 res += (cnt[a[x]] * cnt[a[x]]) - (cnt[a[x]] - 1) * (cnt[a[x]] - 1);
5 }
6 void sub(int x)
7 {
8 cnt[a[x]]--;
9 res -= (cnt[a[x]] + 1) * (cnt[a[x]] + 1) - (cnt[a[x]]) * (cnt[a[x]]);
10 }
时间复杂度:在n元素长度与m操作次数,阶数相同的情况下为O(nsqrt(n));

浙公网安备 33010602011771号