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

 

 所谓离线的意思是指:先读取全部的输入,经过算法的一系列操作,最后在按照读入的顺序输出答案

相对的,在线的意思是:读取一行输入,输出一行对应的答案

《分块》

由于莫队的核心思想是分块,所以先说一下分块这个思想:

 

 

 

 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));

posted @ 2022-07-30 23:24  次林梦叶  阅读(45)  评论(0)    收藏  举报