离散化

知识点

离散化就是将无穷大集合中的若干个元素映射为有限集合以便于统计。
通常表现为取值范围非常大,但是数目比较有限,并且不关心数值的绝对大小(只把数值作为代表,或者只和它们的相对顺序有关),此时就可以把m个数映射为1~m,这样一来,时间、空间复杂度降至与m有关。

模板

  1. // C++ STL写法
  2. void discrete() {
  3. vector<int> alls; // 存储所有待离散化的值
  4. sort(alls.begin(), alls.end()); // 将所有值排序
  5. alls.erase(unique(alls.begin(), alls.end()), alls.end()); // 去掉重复元素
  6. }
  7. // 二分求出x对应的离散化的值
  8. int find(int x) // 找到第一个大于等于x的位置
  9. {
  10. int l = 0, r = m - 1;
  11. while (l < r)
  12. {
  13. int mid = l + r >> 1;
  14. if (alls[mid] >= x) r = mid;
  15. else l = mid + 1;
  16. }
  17. return r + 1; // 映射到1, 2, ...n
  18. }
  19. // 通用写法
  20. void discrete() {
  21. sort(alls + 1, alls + n + 1);
  22. for (int i = 1; i <= n; i++)
  23. if (i == 1 || alls[i] != alls[i - 1])
  24. alls[m++] = alls[i];
  25. }
  26. int query(int x) { // 或者像上面一样写一个二分
  27. return lower_bound(alls, alls + m, x) - alls + 1;
  28. }

习题

电影

如果只是用于计数的话,离散化没什么必要,直接用哈希表就好了,本质上是一样的

  1. // 离散化
  2. #include <iostream>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 2e5 + 10;
  6. int n, m, cnt, dcnt;
  7. int a[N], b[N], c[N], all[3*N], dis[3*N], sum[3*N];
  8. void discrete() {
  9. sort(all, all + cnt);
  10. for (int i = 0; i < cnt; i++) {
  11. if (i == 0 || all[i] != all[i - 1])
  12. dis[dcnt++] = all[i];
  13. }
  14. }
  15. int query(int x) {
  16. return lower_bound(dis, dis + dcnt, x) - dis;
  17. }
  18. int main() {
  19. cin >> n;
  20. for (int i = 0; i < n; i++) {
  21. scanf("%d", &a[i]);
  22. all[cnt++] = a[i];
  23. }
  24. cin >> m;
  25. for (int i = 0; i < m; i++) {
  26. scanf("%d", &b[i]);
  27. all[cnt++] = b[i];
  28. }
  29. for (int i = 0; i < m; i++) {
  30. scanf("%d", &c[i]);
  31. all[cnt++] = c[i];
  32. }
  33. discrete();
  34. for (int i = 0; i < n; i++) {
  35. int id = query(a[i]);
  36. sum[id]++;
  37. }
  38. int max1 = -1, max2 = -1, idx = -1;
  39. for (int i = 0; i < m; i++) {
  40. int id1 = query(b[i]), id2 = query(c[i]);
  41. if (sum[id1] > max1) {
  42. max1 = sum[id1];
  43. max2 = sum[id2];
  44. idx = i + 1;
  45. }
  46. else if (sum[id1] == max1 && sum[id2] > max2) {
  47. max2 = sum[id2];
  48. idx = i + 1;
  49. }
  50. }
  51. cout << idx;
  52. return 0;
  53. }
  54. // 哈希表
  55. #include <iostream>
  56. #include <algorithm>
  57. #include <unordered_map>
  58. using namespace std;
  59. const int N = 2e5 + 10;
  60. int n, m;
  61. unordered_map<int, int> a;
  62. struct MV {
  63. int idx, cnt1, cnt2;
  64. bool operator <(const MV &m) const {
  65. if (cnt1 == m.cnt1) return cnt2 < m.cnt2;
  66. return cnt1 < m.cnt1;
  67. }
  68. } mv[N];
  69. int main() {
  70. cin >> n;
  71. for (int i = 1; i <= n; i++) {
  72. int x;
  73. scanf("%d", &x);
  74. a[x]++; //完成了离散化的工作
  75. }
  76. cin >> m;
  77. for (int i = 1; i <= m; i++) {
  78. int x;
  79. scanf("%d", &x);
  80. mv[i].idx = i;
  81. mv[i].cnt1 = a[x];
  82. }
  83. for (int i = 1; i <= m; i++) {
  84. int x;
  85. scanf("%d", &x);
  86. mv[i].cnt2 = a[x];
  87. }
  88. sort(mv + 1, mv + m + 1);
  89. cout << mv[m].idx;
  90. return 0;
  91. }

区间和

由于需要遍历数组获取前缀和,而且数组范围过大,因此需要使用离散化
只用哈希表仍然需要遍历,虽然可以再用一个数组记录有数的位置,但是这样做就有些太麻烦了

  1. #include <iostream>
  2. #include <vector>
  3. #include <algorithm>
  4. using namespace std;
  5. const int N = 3 * 1e5 + 10;
  6. typedef pair<int, int> PII;
  7. vector<PII> add, query;
  8. // vector<int> alls;
  9. int alls[N], all_size, dsize;
  10. int a[N], s[N];
  11. void discrete() {
  12. sort(alls, alls + all_size);
  13. for (int i = 0; i < all_size; i++) {
  14. if (i == 0 || alls[i] != alls[i - 1])
  15. alls[dsize++] = alls[i];
  16. }
  17. }
  18. int find(int x) {
  19. return lower_bound(alls, alls + dsize, x) - alls + 1;
  20. }
  21. // int find(int x) {
  22. // int l = 0, r = alls.size() - 1;
  23. // while (l < r) {
  24. // int mid = l + (r - l) / 2;
  25. // if (alls[mid] >= x) r = mid;
  26. // else l = mid + 1;
  27. // }
  28. // return r + 1;
  29. // }
  30. int main() {
  31. int n, m;
  32. scanf("%d%d", &n, &m);
  33. for (int i = 0; i < n; i++) {
  34. int x, c;
  35. scanf("%d%d", &x, &c);
  36. add.push_back({x, c});
  37. // alls.push_back(x);
  38. alls[all_size++] = x;
  39. }
  40. for (int i = 0; i < m; i++) {
  41. int l, r;
  42. scanf("%d%d", &l, &r);
  43. query.push_back({l, r});
  44. alls[all_size++] = l;
  45. alls[all_size++] = r;
  46. // alls.push_back(l);
  47. // alls.push_back(r);
  48. }
  49. discrete();
  50. // sort(alls.begin(), alls.end());
  51. // alls.erase(unique(alls.begin(), alls.end()), alls.end());
  52. for (auto item: add) {
  53. int x = find(item.first);
  54. a[x] += item.second;
  55. }
  56. for (int i = 1; i <= dsize; i++) s[i] = s[i - 1] + a[i];
  57. for (auto item: query) {
  58. int l = find(item.first), r = find(item.second);
  59. printf("%d\n", s[r] - s[l - 1]);
  60. }
  61. return 0;
  62. }

中位数

知识点

直接看模板题
货仓选址

  1. #include <iostream>
  2. #include <algorithm>
  3. using namespace std;
  4. const int N = 1e5 + 10;
  5. int a[N];
  6. int main() {
  7. int n;
  8. cin >> n;
  9. for (int i = 0; i < n; i++)
  10. scanf("%d", &a[i]);
  11. sort(a, a + n);
  12. int ans = 0;
  13. for (int i = 0; i < n; i++) ans += abs(a[i] - a[n / 2]);
  14. cout << ans;
  15. return 0;
  16. }

可以将其一般化为这样一个问题:给定x[1], …, x[n],求排序 - 图1的最小值,其中x∈R

  • n为奇数时,取中位数
  • n为偶数时,取x[n/2]~x[n/2+1]中的任何一个数均可

    习题

    第k大数

    知识点

    基于快排的快速选择算法,做法就是在基准值的位置放好之后,如果大于基准值的数的数量cnt>=k,就在左半段寻找第k大数,否则在右半段寻找第k-cnt大数。每次选择了左右之一继续,因此时间复杂度为排序 - 图2

    模板

    ```cpp // 快排模板 void quick_sort(int q[], int l, int r) { if (l >= r) return;

    // 随机选取基准 int idx = l + rand() % (r - l + 1); int x = q[idx]; int i = l - 1, j = r + 1;

    while (i < j) { //x左边的数x

    1. while (q[++i] < x);
    2. while (q[--j] > x);
    3. if (i < j) swap(q[i], q[j]);

    }

    quick_sort(q, l, j); quick_sort(q, j + 1, r); }

// 快速选择 void quick_select(int q[], int l, int r, int k) { if (l >= r) return q[l];

  1. int idx = l + rnad() % (r - l + 1);
  2. int x = q[idx];
  3. int i = l - 1, j = r + 1;
  4. while (i < j) {
  5. while (q[++i] < x);
  6. while (q[--j] > x);
  7. if (i < j) swap(q[i], q[j]);
  8. }
  9. int tmp = j - l + 1;
  10. if (tmp < k) return quick_select(q, j + 1, r, k - tmp);
  11. else return quick_select(q, l, j, k);

}

  1. <a name="WIflP"></a>
  2. # 逆序对
  3. <a name="kBGpS"></a>
  4. ## 知识点
  5. 定义:对于一个序列a,若i<j并且a[i]>a[j],则称a[i]与a[j]构成逆序对<br />使用归并排序可以在O(nlogn)时间求出逆序对个数<br />左右两半各自内部的逆序对数目作为子问题,只考虑merge即可
  6. <a name="zDOr5"></a>
  7. ## 模板
  8. ```cpp
  9. // 归并排序
  10. void merge_sort(int a[], int l, int r) {
  11. if (l >= r) return;
  12. int mid = l + r >> 1;
  13. merge_sort(a, l, mid);
  14. merge_sort(a, mid + 1, r);
  15. int i = l, j = mid + 1, k = 0;
  16. while (i <= mid && j <= r) {
  17. if (a[i] <= a[j]) tmp[k++] = a[i++];
  18. else tmp[k++] = a[j++];
  19. }
  20. while (i <= mid) tmp[k++] = a[i++];
  21. while (j <= r) tmp[k++] = a[j++];
  22. for (int i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];
  23. }
  24. // 求逆序对数目
  25. typedef long long LL;
  26. LL merge_sort(int a[], int l, int r) {
  27. if (l >= r) return;
  28. int mid = l + r >> 1;
  29. LL cnt = merge_sort(a, l, mid);
  30. cnt += merge_sort(a, mid + 1, r);
  31. int i = l, j = mid + 1, k = 0;
  32. while (i <= mid && j <= r) {
  33. if (a[i] <= a[j]) {
  34. tmp[k++] = a[i++];
  35. cnt += mid - i + 1;
  36. }
  37. else tmp[k++] = a[j++];
  38. }
  39. while (i <= mid) tmp[k++] = a[i++];
  40. while (j <= r) tmp[k++] = a[j++];
  41. for (int i = l, j = 0; i <= r; i++, j++) a[i] = tmp[j];
  42. return cnt;
  43. }

STL

大多数情况下,排序直接使用C++自带的sort,通过自定义排序函数cmp来解决各种问题

  • 对字符串排序,默认的排序方法和strcmp一致,逐char比较
    • strcmp:相等时返回0,ab返回正数
    • 需要比较字符串长度的话使用strlen

      习题

      字符串排序2

      非英文字母的其它字符保持原来的位置
      可以将英文字母单独拿出来排序
      最后如果是英文字母,按照单独拿出来的排序结果依次补上,不是英文字母直接用 ```cpp

      include

      include

      include

      include

      include

using namespace std;

const int N = 1005;

struct alpha { char c; int id; }alphas[N];

char s[N];

bool cmp(alpha a, alpha b) { char c1 = a.c, c2 = b.c; if (c1 >= ‘A’ && c1 <= ‘Z’) c1 = c1 - ‘A’ + ‘a’; if (c2 >= ‘A’ && c2 <= ‘Z’) c2 = c2 - ‘A’ + ‘a’;

  1. if (c1 == c2) return a.id < b.id;
  2. return c1 < c2;

}

int main() {

  1. #ifdef SUBMIT
  2. freopen("in.txt", "r", stdin);
  3. freopen("out.txt", "w", stdout);
  4. long _begin_time = clock();
  5. #endif
  6. while (gets(s)) {
  7. int len = strlen(s);
  8. int cnt = 0;
  9. for (int i = 0; i < len; i++) {
  10. if (isalpha(s[i])) {
  11. alphas[cnt].c = s[i];
  12. alphas[cnt].id = cnt;
  13. cnt++;
  14. }
  15. }
  16. sort(alphas, alphas + cnt, cmp);
  17. cnt = 0;
  18. for (int i = 0; i < len; i++) {
  19. if (isalpha(s[i])) {
  20. printf("%c", alphas[cnt++]);
  21. }
  22. else {
  23. printf("%c", s[i]);
  24. }
  25. }
  26. puts("");
  27. }
  28. #ifdef SUBMIT
  29. long _end_time = clock();
  30. printf("\n\ntime = %ld ms", _end_time - _begin_time);
  31. #endif
  32. return 0;

} ```

TOPK

  • 全局排序,O(n*lg(n))
  • 局部排序,只排序TopK个数,O(n*k)
    • 选择排序选出最小的K个数
  • 堆,TopK个数也不排序了,O(n*lg(k))
    • 选出的K个数不需要顺序,可以维护一个大小为K的堆
    • 建堆复杂度O(k),调整复杂度最小O(n),最大O(nlogk),总体最小O(n),最大O(nlogk)
  • 快速选择:选出第K大的数,那么它左侧加上它就是TOPK

    • 加上随机划分,复杂度可以降至O(n)
    • 空间开销大,需要把所有数都读进来

      稳定排序

  • 使用stable_sort,用法和sort完全一致,是稳定的

    • stable_sort 基于归并排序,属于非原地排序,时间复杂度上与sort相差无几,当初做实验非递归的版本甚至要快
  • 为每个元素增加一个属性下标,进行区分,利用cmp比较规则