• 【数据结构】—— Trie/并查集/堆



    Trie

    Trie(字典树)是一种用于实现字符串快速检索的多叉树结构。Trie树的每个结点都拥有若干个字符指针,若在插入或者检索字符串时扫描到一个字符 c,就沿着当前结点的 c 字符指针,走向该节点指向的节点。


    AcWing 835. Trie字符串统计

    输入样例:

    1. 5
    2. I abc
    3. Q abc
    4. Q ab
    5. I ab
    6. Q ab

    输出样例:

    1. 1
    2. 0
    3. 1

    插入操作

    1. void insert(char str[])
    2. {
    3. int p = 0;
    4. for(int i = 0; str[i]; i ++ )
    5. {
    6. int u = str[i] - 'a';
    7. // 不存在节点就创建一个结点
    8. if(!son[p][u]) son[p][u] = ++ idx;
    9. p = son[p][u];
    10. }
    11. // 结尾标记
    12. cnt[p] ++ ;
    13. }

    查询操作 

    1. int query(char str[])
    2. {
    3. int p = 0;
    4. for(int i = 0; str[i]; i ++ )
    5. {
    6. int u = str[i] - 'a';
    7. if(!son[p][u]) return 0;
    8. p = son[p][u];
    9. }
    10. return 1;
    11. }

    https://www.acwing.com/solution/content/14695/ 


    1. #include
    2. using namespace std;
    3. const int N = 100010;
    4. // 下标是0的点既是根节点,又是空节点
    5. int son[N][26], cnt[N], idx;
    6. char str[N];
    7. void insert(char str[])
    8. {
    9. int p = 0;
    10. for(int i = 0; str[i]; i ++ )
    11. {
    12. int u = str[i] - 'a';
    13. // 不存在节点就创建一个结点
    14. if(!son[p][u]) son[p][u] = ++ idx;
    15. p = son[p][u];
    16. }
    17. // 结尾标记
    18. cnt[p] ++ ;
    19. }
    20. int query(char str[])
    21. {
    22. int p = 0;
    23. for(int i = 0; str[i]; i ++ )
    24. {
    25. int u = str[i] - 'a';
    26. if(!son[p][u]) return 0;
    27. p = son[p][u];
    28. }
    29. return cnt[p];
    30. }
    31. int main()
    32. {
    33. int n; cin >> n;
    34. while(n -- )
    35. {
    36. char op[2];
    37. scanf("%s%s", op, str);
    38. if(op[0] == 'I') insert(str);
    39. else printf("%d\n", query(str));
    40. }
    41. return 0;
    42. }

    AcWing 143. 最大异或对 

    输入样例:

    1. 3
    2. 1 2 3

    输出样例:

    3

    暴力做法 

    1. int res = 0;
    2. for(int i = 0; i < n; i ++ )
    3. {
    4. for(int j = 0; j < i; j ++ )
    5. res = max(res, a[j] ^ a[i]);
    6. }

    优化 

    第二层循环等价于在 a0 ~ ai 找一个异或对最大的数,这里可以采用Trie树的结构来优化

    其实来说,一个整数,是可以转化成为一个32位的二进制数,而也就可以变成长度为32位的二进制字符串.
    既然如此话,那么我们可以这么做,每一次检索的时候,我们都走与当前AiAi这一位相反的位置走,也就是让Xor值最大,如果说没有路可以走的话,那么就走相同的路.

    1. #include
    2. #include
    3. using namespace std;
    4. const int N = 100010,M = 31 * N;
    5. int n;
    6. int a[N];
    7. int son[M][2],idx;
    8. void insert(int x)
    9. {
    10. int p = 0;
    11. for(int i = 30;i >= 0;i --)
    12. {
    13. int u = x >> i & 1;
    14. if(!son[p][u]) son[p][u] = ++ idx;
    15. p = son[p][u];
    16. }
    17. }
    18. int query(int x)
    19. {
    20. int p = 0,res = 0;
    21. for(int i = 30;i >= 0;i --)
    22. {
    23. int u = x >> i & 1;
    24. if(son[p][!u]) {
    25. p = son[p][!u];
    26. res = res * 2 + !u;
    27. }else {
    28. p = son[p][u];
    29. res = res * 2 + u;
    30. }
    31. }
    32. return res;
    33. }
    34. int main()
    35. {
    36. cin >> n;
    37. for(int i = 0;i < n;i ++) scanf("%d",&a[i]);
    38. int res = 0;
    39. for(int i = 0;i < n;i ++)
    40. {
    41. //减少一次特判
    42. insert(a[i]);
    43. int t = query(a[i]);
    44. res = max(res,a[i] ^ t);
    45. }
    46. cout << res;
    47. return 0;
    48. }

    并查集

    并查集(Disjoint-Set)是一种可以动态维护若干个不重叠的集合,并支持合并与查询的数据结构,主要可以实现以下两个操作:

    1. 将两个集合合并
    2. 询问两个元素是否在一个集合中

    并查集用一个树形结构来存储每个集合,树上的每个节点都是一个元素,树根是集合的代表元素。整个并查集就是一个森林(若干棵树)。

    每个集合用一棵树来表述。树根的编号就是整个集合的编号。每个节点存储它的父节点,p[x]表示x的父节点

    问题1:如何判断树根:p[x] == x

    问题2:如何求 x 的集合编号 :while(p[x] != x) x = p[x]

    问题3:如何合并两个集合:pa是x的集合编号,pb是y的集合编号,p[pa] =pb


    AcWing 836. 合并集合 

    输入样例:

    1. 4 5
    2. M 1 2
    3. M 3 4
    4. Q 1 2
    5. Q 1 3
    6. Q 3 4

    输出样例:

    1. Yes
    2. No
    3. Yes

     https://www.acwing.com/solution/content/33345/

    初始化

    for(int i = 1; i <= n; i ++ ) p[i] = i;

    查找 + 路径压缩 

    1. int find(int x){ //返回x的祖先节点 + 路径压缩
    2. //祖先节点的父节点是自己本身
    3. if(p[x] != x){
    4. //将x的父亲置为x父亲的祖先节点,实现路径的压缩
    5. p[x] = find(p[x]);
    6. }
    7. return p[x];
    8. }

     合并操作

    p[find(a)] = find(b)

     

     


    1. #include
    2. using namespace std;
    3. const int N = 100010;
    4. int n, m;
    5. int p[N];
    6. int find(int x)
    7. {
    8. if(x != p[x]) p[x] = find(p[x]);
    9. return p[x];
    10. }
    11. int main()
    12. {
    13. cin >> n >> m;
    14. for(int i = 1; i <= n; i ++ ) p[i] = i;
    15. while(m -- )
    16. {
    17. char op[2];
    18. int a, b;
    19. scanf("%s%d%d", op, &a, &b);
    20. if(op[0] == 'M') p[find(a)] = find(b);
    21. else
    22. {
    23. if(find(a) == find(b)) puts("Yes");
    24. else puts("No");
    25. }
    26. }
    27. return 0;
    28. }


    AcWing 837. 连通块中点的数量 

    输入样例:

    1. 5 5
    2. C 1 2
    3. Q1 1 2
    4. Q2 1
    5. C 2 5
    6. Q2 5

    输出样例:

    1. Yes
    2. 2
    3. 3

    注意事项:当两个点已经是连通的状态时,再在这两个点直接连边的时候,不能增加连通块点的数量


    1. #include
    2. using namespace std;
    3. const int N = 100010;
    4. int n, m;
    5. int p[N], cnt[N];
    6. int find(int x)
    7. {
    8. if (p[x] != x) p[x] = find(p[x]);
    9. return p[x];
    10. }
    11. int main()
    12. {
    13. cin >> n >> m;
    14. for (int i = 1; i <= n; i ++ )
    15. {
    16. p[i] = i;
    17. cnt[i] = 1;
    18. }
    19. while (m -- )
    20. {
    21. string op;
    22. int a, b;
    23. cin >> op;
    24. if (op == "C")
    25. {
    26. cin >> a >> b;
    27. a = find(a), b = find(b);
    28. if (a != b)
    29. {
    30. p[a] = b;
    31. cnt[b] += cnt[a];
    32. }
    33. }
    34. else if (op == "Q1")
    35. {
    36. cin >> a >> b;
    37. if (find(a) == find(b)) puts("Yes");
    38. else puts("No");
    39. }
    40. else
    41. {
    42. cin >> a;
    43. cout << cnt[find(a)] << endl;
    44. }
    45. }
    46. return 0;
    47. }

    AcWing 240. 食物链 

    输入样例:

    1. 100 7
    2. 1 101 1
    3. 2 1 2
    4. 2 2 3
    5. 2 3 3
    6. 1 1 3
    7. 2 3 1
    8. 1 5 5

    输出样例:

    3

    TIPS:记录每个点和根节点之间的关系

    余1:可以吃根节点

    余2:可以被根节点吃

    余0:与根节点是-同类


     

    1. #include
    2. using namespace std;
    3. const int N = 50010;
    4. int n, m;
    5. int p[N], d[N];
    6. int find(int x)
    7. {
    8. if (p[x] != x)
    9. {
    10. int t = find(p[x]);
    11. d[x] += d[p[x]];
    12. p[x] = t;
    13. }
    14. return p[x];
    15. }
    16. int main()
    17. {
    18. scanf("%d%d", &n, &m);
    19. for (int i = 1; i <= n; i ++ ) p[i] = i;
    20. int res = 0;
    21. while (m -- )
    22. {
    23. int t, x, y;
    24. scanf("%d%d%d", &t, &x, &y);
    25. if (x > n || y > n) res ++ ;
    26. else
    27. {
    28. int px = find(x), py = find(y);
    29. if (t == 1)
    30. {
    31. if (px == py && (d[x] - d[y]) % 3) res ++ ;
    32. else if (px != py)
    33. {
    34. p[px] = py;
    35. d[px] = d[y] - d[x];
    36. }
    37. }
    38. else
    39. {
    40. if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;
    41. else if (px != py)
    42. {
    43. p[px] = py;
    44. d[px] = d[y] + 1 - d[x];
    45. }
    46. }
    47. }
    48. }
    49. printf("%d\n", res);
    50. return 0;
    51. }

    堆 

    从二叉堆的结构说起,它是一棵二叉树,并且是完全二叉树,每个结点中存有一个元素(或者说,有个权值)。

    堆性质:父亲的权值不小于儿子的权值(大根堆)。同样的,我们可以定义小根堆。本文以大根堆为例。

    由堆性质,树根存的是最大值


    考虑使用一个序列  \small h 来表示堆。 \small h_i 的两个儿子分别是  \small h_{2i} 和 \small h_{2i+1}, 1 是根结点


    如何手写一个堆:

    1.插入一个数: \small heap[++ size] = x; up(size)

    2.求集合当中的最小数:\small heap[1]

    3.删除最小数:\small heap[1] = heap[size]; size--;down(1)

    4.删除任意一个元素:\small heap[k] = heap[size];size--;up(k);down(k)

    5.修改任意一个\small heap[k]=x;down(k);up(k)


    AcWing 838. 堆排序 

    输入样例:

    1. 5 3
    2. 4 5 1 3 2

    输出样例:

    1 2 3

    AcWing 838. 堆排序 分析i=n/2 - AcWing


    1. #include
    2. #include
    3. using namespace std;
    4. const int N = 100010;
    5. int n, m;
    6. int h[N], size;
    7. void down(int u)
    8. {
    9. int t = u;
    10. if(u * 2 <= size && h[u * 2] < h[t] ) t = u * 2;
    11. if(u * 2 + 1<= size && h[u * 2 + 1] < h[t] ) t = u * 2 + 1;
    12. if(u != t)
    13. {
    14. swap(h[u], h[t]);
    15. down(t);
    16. }
    17. }
    18. int main()
    19. {
    20. cin >> n >> m;
    21. for(int i = 1; i <= n ; i ++ ) scanf("%d", h[i]);
    22. for(int i = n / 2; i; i -- ) down(i);
    23. while(m -- )
    24. {
    25. printf("%d ", h[1]);
    26. h[1] = h[size --];
    27. down(1);
    28. }
    29. }

     AcWing 839. 模拟堆

    输入样例:

    1. 8
    2. I -10
    3. PM
    4. I -10
    5. D 1
    6. C 2 8
    7. I 6
    8. PM
    9. DM

    输出样例:

    1. -10
    2. 6

    1. #include
    2. #include
    3. #include
    4. using namespace std;
    5. const int N = 100010;
    6. int h[N], ph[N], hp[N], cnt;
    7. void heap_swap(int a, int b)
    8. {
    9. swap(ph[hp[a]],ph[hp[b]]);
    10. swap(hp[a], hp[b]);
    11. swap(h[a], h[b]);
    12. }
    13. void down(int u)
    14. {
    15. int t = u;
    16. if (u * 2 <= cnt && h[u * 2] < h[t]) t = u * 2;
    17. if (u * 2 + 1 <= cnt && h[u * 2 + 1] < h[t]) t = u * 2 + 1;
    18. if (u != t)
    19. {
    20. heap_swap(u, t);
    21. down(t);
    22. }
    23. }
    24. void up(int u)
    25. {
    26. while (u / 2 && h[u] < h[u / 2])
    27. {
    28. heap_swap(u, u / 2);
    29. u >>= 1;
    30. }
    31. }
    32. int main()
    33. {
    34. int n, m = 0;
    35. scanf("%d", &n);
    36. while (n -- )
    37. {
    38. char op[5];
    39. int k, x;
    40. scanf("%s", op);
    41. if (!strcmp(op, "I"))
    42. {
    43. scanf("%d", &x);
    44. cnt ++ ;
    45. m ++ ;
    46. ph[m] = cnt, hp[cnt] = m;
    47. h[cnt] = x;
    48. up(cnt);
    49. }
    50. else if (!strcmp(op, "PM")) printf("%d\n", h[1]);
    51. else if (!strcmp(op, "DM"))
    52. {
    53. heap_swap(1, cnt);
    54. cnt -- ;
    55. down(1);
    56. }
    57. else if (!strcmp(op, "D"))
    58. {
    59. scanf("%d", &k);
    60. k = ph[k];
    61. heap_swap(k, cnt);
    62. cnt -- ;
    63. up(k);
    64. down(k);
    65. }
    66. else
    67. {
    68. scanf("%d%d", &k, &x);
    69. k = ph[k];
    70. h[k] = x;
    71. up(k);
    72. down(k);
    73. }
    74. }
    75. return 0;
    76. }

     

  • 相关阅读:
    算法day31|455,476,53
    Linux中网络排查命令traceroute
    nginx 负载均衡
    抽象类与接口
    生产脚本1
    解读vue3源码-响应式篇1(手写一个简单的响应式函数)
    Eclipse 主网即将上线迎空投预期,Zepoch 节点或成受益者?
    05-Redis 持久化之RDB 的奥秘
    【JAVA程序设计】(C00082)基于SSM+uniapp前后端分离的美发服务平台微信小程序
    CDN网络科普小文(故事版)
  • 原文地址:https://blog.csdn.net/forever_bryant/article/details/126048173