• 玩链子游戏


    一 游戏描述

    有一条链子,上面有 n 颗钻石,钻石编号为 1~n 。可以对该链子执行两种操作:

    ① CUT a b c (区间切割操作)

    切下从第 a 颗钻石到第 b 颗钻石的链子,把它插在剩余链子的第 c 颗钻石后面;比如 n 等于8,链子是 1, 2, 3, 4, 5, 6, 7, 8,对该链子执行 CUT 3 5 4,会切下 3, 4, 5 链子,剩下 1, 2, 6, 7, 8 链子,把 3, 4, 5 链子插入第 4 颗钻石之后,现在的链子是 1, 2, 6, 7,3, 4, 5, 8;

    ② FLIP a b (区间反转操作)

    切下从第 a 颗钻石到第 b 颗钻石的链子,把链子倒过来放回原来的位置,比如在链子 1, 2,6, 7, 3, 4, 5, 8 上执行 FLIP 2 6,则得到的链子是1, 4, 3, 7, 6,2, 5, 8。那么执行 m 种操作后,链子的外观是怎样的呢?

    二 输入

    输入包括多个测试用例,在测试用例的第 1 行都输入两个数字 n 和 m(1≤n ,m≤3×10^5 ),分别表示链子的钻石总数和操作次数。接下来的 m 行,每行都输入 CUT a b c 或者 FLIP a b 。CUT a b c 表示切割操作,1≤a≤b≤n,0≤c≤n-(b-a +1);FLIP a b 表示反转操作,1≤a

    三 输出

    对每个测试用例,都输出一行 n 个数字,第 i 个数字是链子上第 i 颗钻石的编号。

    四 输入和输出样例

    1 输入样例

    8 2

    CUT 3 5 4

    FLIP 2 6

    -1 -1

    2 输出样例

    1 4 3 7 6 2 5 8

    五 分析

    本游戏包括区间切割(CUT a b c )、区间反转(FLIP a b)两种操作。这里以输入测试用例“8 2”进行分析,过程如下。

    1 链子的初始状态如下

    2 执行 CUT 3 5 4

    切下从第 3 颗钻石到第 5 颗钻石的链子,把它插入剩余链子上第4颗钻石的后面。

    3 执行 FLIP 2 6

    切下从第 2 颗钻石到第 6 颗钻石的链子,把它倒过来放回原来的位置。

    六 算法设计

    1 创建伸展树,因为要进行区间操作,因此增加两个虚节点。

    2 根据读入的信息,判断是执行区间切割操作还是区间反转操作。

    3 按照中序遍历,输出伸展树。

    七 图解

    1 创建伸展树

    数据序列为 1, 2, 3, 4, 5, 6, 7, 8,创建伸展树时在序列的首尾增加两个虚节点,该节点的值为无穷小 -inf,无穷大 inf(inf 是一个较大的数,如 0x3f3f3f3f)。由于在序列前面添加了一个虚节点,所以原来的位序增加 1,因此对 [l , r ]区间的操作变为对 [++l ,++r] 区间的操作。

    2 区间切割

    首先切割 [l , r] 区间,将 Al-1 旋转到根,然后将Ar +1 旋转到 Al -1 的下方,此时切割的 [l , r ] 区间是 Ar +1 的左子树,用 tmp 暂存。然后查找第 pos 个节点,可以将 Apos 旋转到根部,再将 Apos+1 旋转到 Apos 下方,将 tmp 挂接在 Apos+1 的左子树上即可,相当于将 [l,r ] 区间插入第 pos 个节点之后,如下图所示。

    CUT 3 5 4:因为添加了虚节点,因此切割 [4, 6] 区间,将其插入剩余区间第 5 个节点的后面。将第 3个节点(数字2)旋转到根,然后将第 7 个节点(数字6)旋转到第 3 个节点的下方,此时切割的区间就是第 7 个节点的左子树,用 tmp 暂存。

    然后将第 5 个节点旋转到根部,将第 6 个节点旋转到其下方,将 tmp 挂接在第 6 个节点的左子树上即可,此时序列为1, 2, 6, 7, 3, 4, 5,8,如下图所示。

    3 区间反转

    和区间切割类似,反转 [l , r] 区间时只需将 Al-1 旋转到根,然后将 Ar +1 旋转到 Al-1 的下方,此时需要反转的 [l , r ] 区间就是 Ar+1 的左子树,对该区间的根节点打上反转懒标记即可。

    八 代码

    1. package com.platform.modules.alg.alglib.hdu3487;
    2. public class Hdu3487 {
    3. public String output = "";
    4. private int maxn = 300010;
    5. private int inf = 0x3f3f3f3f;
    6. int n, cnt, root; // 结点数,结点存储下标累计,树根
    7. String op;
    8. boolean flag;
    9. private node tr[] = new node[maxn];
    10. void Update(int x) {
    11. tr[x].size = 1;
    12. if (tr[x].son[0] > 0)
    13. tr[x].size += tr[tr[x].son[0]].size;
    14. if (tr[x].son[1] > 0)
    15. tr[x].size += tr[tr[x].son[1]].size;
    16. }
    17. public Hdu3487() {
    18. for (int i = 0; i < tr.length; i++) {
    19. tr[i] = new node();
    20. }
    21. }
    22. // 下传翻转标记
    23. void Pushdown(int x) {
    24. if (tr[x].rev == 1) {
    25. tr[x].rev ^= 1;
    26. int temp = tr[x].son[0];
    27. tr[x].son[0] = tr[x].son[1];
    28. tr[x].son[1] = temp;
    29. if (tr[x].son[0] > 0)
    30. tr[tr[x].son[0]].rev ^= 1;
    31. if (tr[x].son[1] > 0)
    32. tr[tr[x].son[1]].rev ^= 1;
    33. }
    34. }
    35. // 生成新结点
    36. int New(int father, int val) {
    37. tr[++cnt].fa = father;
    38. tr[cnt].val = val;
    39. tr[cnt].size = 1;
    40. tr[cnt].rev = 0;
    41. tr[cnt].son[0] = tr[cnt].son[1] = 0;
    42. return cnt;
    43. }
    44. // 旋转
    45. void Rotate(int x) {
    46. Pushdown(x);
    47. int y = tr[x].fa, z = tr[y].fa;
    48. ;
    49. int c = (tr[y].son[0] == x) ? 1 : 0;
    50. tr[y].son[1 - c] = tr[x].son[c];
    51. tr[tr[x].son[c]].fa = y;
    52. tr[x].fa = z;
    53. if (z > 0)
    54. tr[z].son[tr[z].son[1] == y ? 1 : 0] = x;
    55. tr[x].son[c] = y;
    56. tr[y].fa = x;
    57. Update(y);
    58. Update(x);
    59. }
    60. // 将 x 旋转为goal的儿子
    61. void Splay(int x, int goal) {
    62. while (tr[x].fa != goal) {
    63. int y = tr[x].fa, z = tr[y].fa;
    64. if (z != goal) {
    65. if (((tr[z].son[0] == y ? 1 : 0) ^ (tr[y].son[0] == x ? 1 : 0)) == 0) {
    66. Rotate(y);
    67. } else {
    68. Rotate(x);
    69. }
    70. }
    71. Rotate(x);
    72. }
    73. // 如果 goal 是 0,则更新根为 x
    74. if (goal == 0) root = x;
    75. }
    76. int Findk(int x, int k) {
    77. while (true) {
    78. Pushdown(x);
    79. int sn = tr[x].son[0] > 0 ? tr[tr[x].son[0]].size + 1 : 1;
    80. if (k == sn)
    81. return x;
    82. if (k > sn) {
    83. k -= sn;
    84. x = tr[x].son[1];
    85. } else
    86. x = tr[x].son[0];
    87. }
    88. }
    89. int Build(int l, int r, int t, int fa) {
    90. if (l > r)
    91. return t;
    92. int mid = l + r >> 1;
    93. t = New(fa, mid);
    94. tr[t].son[0] = Build(l, mid - 1, tr[t].son[0], t);
    95. tr[t].son[1] = Build(mid + 1, r, tr[t].son[1], t);
    96. Update(t);
    97. return t;
    98. }
    99. void Init() {
    100. cnt = root = 0;
    101. tr[0].son[0] = tr[0].son[1] = 0;
    102. root = New(0, -inf); // 创建虚结点1
    103. tr[root].son[1] = New(root, inf); // 创建虚结点2
    104. tr[root].size = 2;
    105. tr[tr[root].son[1]].son[0] = Build(1, n, tr[tr[root].son[1]].son[0], tr[root].son[1]);
    106. Update(tr[root].son[1]);
    107. Update(root);
    108. }
    109. // [l,r]区间切割,插入第c个之后
    110. void Cut(int l, int r, int c) {
    111. int x = Findk(root, l - 1);
    112. int y = Findk(root, r + 1);
    113. Splay(x, 0);
    114. Splay(y, x);
    115. int tmp = tr[y].son[0];
    116. tr[y].son[0] = 0; // 删除
    117. Update(y);
    118. Update(x);
    119. x = Findk(root, c);
    120. y = Findk(root, c + 1);
    121. Splay(x, 0);
    122. Splay(y, x);
    123. tr[y].son[0] = tmp;
    124. tr[tmp].fa = y;
    125. Update(y);
    126. Update(x);
    127. }
    128. // [l,r]区间翻转
    129. void Flip(int l, int r) {
    130. int x = Findk(root, l - 1), y = Findk(root, r + 1);
    131. Splay(x, 0);
    132. Splay(y, x);
    133. tr[tr[y].son[0]].rev ^= 1; // 加翻转标记
    134. }
    135. // 中序遍历测试
    136. void Print(int k) {
    137. Pushdown(k);
    138. if (tr[k].son[0] > 0)
    139. Print(tr[k].son[0]);
    140. if (tr[k].val != -inf && tr[k].val != inf) {
    141. if (flag) {
    142. output += tr[k].val;
    143. flag = false;
    144. } else
    145. output += " " + tr[k].val;
    146. }
    147. if (tr[k].son[1] > 0)
    148. Print(tr[k].son[1]);
    149. }
    150. public String cal(String input) {
    151. int m, a, b, c;
    152. String[] line = input.split("\n");
    153. String[] num = line[0].split(" ");
    154. n = Integer.parseInt(num[0]);
    155. m = Integer.parseInt(num[1]);
    156. Init();
    157. int count = 1;
    158. while (m-- > 0) {
    159. String[] command = line[count++].split(" ");
    160. op = command[0];
    161. if (op.charAt(0) == 'C') {
    162. a = Integer.parseInt(command[1]);
    163. b = Integer.parseInt(command[2]);
    164. c = Integer.parseInt(command[3]);
    165. Cut(++a, ++b, ++c);
    166. } else if (op.charAt(0) == 'F') {
    167. a = Integer.parseInt(command[1]);
    168. b = Integer.parseInt(command[2]);
    169. Flip(++a, ++b);
    170. }
    171. }
    172. flag = true;
    173. Print(root);
    174. return output;
    175. }
    176. }
    177. class node {
    178. int son[] = new int[2];// 左右孩子0,1
    179. int val, fa; //值,父亲
    180. int size, rev; //大小,翻转标记
    181. }

    九 测试

  • 相关阅读:
    defineProperty 与 Proxy 的区别
    linux环境下的nc(ncat的简写)命令用法和udp端口检测
    SpringBoot加载配置文件的顺序
    【Linux kernel/cpufreq】framework ----初识
    (高阶) Redis 7 第14讲 数据统计分析 实战篇
    ESP32学习笔记 - 基于 ESP32 移植 LVGL8.3
    C++面试八股文:用过STL吗?
    中贝通信-603220 三季报分析(20231120)
    从头开始实现一个留言板-README
    景联文科技:AI大模型强势赋能,助力自动驾驶迭代升级
  • 原文地址:https://blog.csdn.net/chengqiuming/article/details/128055153