00-把相同的一段装进一个盒子.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. #!/usr/bin/env python3
  2. """课前零食:用可见段卡模拟 ODT 的 split 与区间操作。"""
  3. from __future__ import annotations
  4. from dataclasses import dataclass
  5. @dataclass
  6. class Segment:
  7. left: int
  8. right: int
  9. value: int
  10. @property
  11. def length(self) -> int:
  12. return self.right - self.left + 1
  13. def show(self) -> str:
  14. return f"[{self.left},{self.right},v={self.value}]"
  15. class SegmentLab:
  16. def __init__(self) -> None:
  17. self.model = [1, 1, 1, 4, 4, 2, 2, 2, 2]
  18. self.segments = [
  19. Segment(1, 3, 1),
  20. Segment(4, 5, 4),
  21. Segment(6, 9, 2),
  22. ]
  23. @property
  24. def n(self) -> int:
  25. return len(self.model)
  26. def normalize(self) -> None:
  27. merged: list[Segment] = []
  28. for segment in self.segments:
  29. if (
  30. merged
  31. and merged[-1].right + 1 == segment.left
  32. and merged[-1].value == segment.value
  33. ):
  34. merged[-1].right = segment.right
  35. else:
  36. merged.append(Segment(segment.left, segment.right, segment.value))
  37. self.segments = merged
  38. def split(self, position: int, log: list[str]) -> int:
  39. if not 1 <= position <= self.n + 1:
  40. raise ValueError("切分位置应在 1..n+1")
  41. if position == self.n + 1:
  42. log.append("split(n+1):这是尾后边界,不新建段。")
  43. return len(self.segments)
  44. for index, segment in enumerate(self.segments):
  45. if segment.left <= position <= segment.right:
  46. if position == segment.left:
  47. log.append(f"split({position}):刚好在 {segment.show()} 左端,什么也不切。")
  48. return index
  49. left_piece = Segment(segment.left, position - 1, segment.value)
  50. right_piece = Segment(position, segment.right, segment.value)
  51. self.segments[index : index + 1] = [left_piece, right_piece]
  52. log.append(
  53. f"split({position}):{segment.show()} 变成 "
  54. f"{left_piece.show()} 与 {right_piece.show()}。"
  55. )
  56. return index + 1
  57. raise RuntimeError("段卡没有完整覆盖数组")
  58. def bounds(self, left: int, right: int, log: list[str]) -> tuple[int, int]:
  59. if not 1 <= left <= right <= self.n:
  60. raise ValueError("区间应满足 1 ≤ l ≤ r ≤ n")
  61. end_position = right + 1
  62. self.split(end_position, log)
  63. begin = self.split(left, log)
  64. # C++ set 的 end 迭代器在 split(left) 后仍有效;本实验室为可读性
  65. # 使用列表,左侧插入可能令整数下标右移,故重新定位同一边界。
  66. if end_position == self.n + 1:
  67. end = len(self.segments)
  68. else:
  69. end = next(
  70. index
  71. for index, segment in enumerate(self.segments)
  72. if segment.left == end_position
  73. )
  74. return begin, end
  75. def add(self, left: int, right: int, delta: int) -> list[str]:
  76. log: list[str] = []
  77. begin, end = self.bounds(left, right, log)
  78. touched = end - begin
  79. for index in range(begin, end):
  80. self.segments[index].value += delta
  81. for index in range(left - 1, right):
  82. self.model[index] += delta
  83. self.normalize()
  84. log.append(f"区间加触及 {touched} 张完整段卡;规范化后共有 {len(self.segments)} 段。")
  85. return log
  86. def assign(self, left: int, right: int, value: int) -> list[str]:
  87. log: list[str] = []
  88. begin, end = self.bounds(left, right, log)
  89. removed = end - begin
  90. self.segments[begin:end] = [Segment(left, right, value)]
  91. self.model[left - 1 : right] = [value] * (right - left + 1)
  92. self.normalize()
  93. log.append(
  94. f"赋值删除中央 {removed} 张卡,插入一张 [{left},{right},v={value}];"
  95. f"规范化后共有 {len(self.segments)} 段。"
  96. )
  97. return log
  98. def kth(self, left: int, right: int, rank: int) -> tuple[int, list[str]]:
  99. log: list[str] = []
  100. begin, end = self.bounds(left, right, log)
  101. pieces = sorted(
  102. (self.segments[index].value, self.segments[index].length)
  103. for index in range(begin, end)
  104. )
  105. total = sum(length for _, length in pieces)
  106. if not 1 <= rank <= total:
  107. raise ValueError("k 超出区间长度")
  108. original_rank = rank
  109. for value, length in pieces:
  110. if rank <= length:
  111. log.append(f"按值排序后的段贡献为 {pieces};第 {original_rank} 小落在值 {value}。")
  112. return value, log
  113. rank -= length
  114. raise RuntimeError("长度账本损坏")
  115. def power_sum(self, left: int, right: int, exponent: int, modulus: int) -> tuple[int, list[str]]:
  116. if modulus <= 0:
  117. raise ValueError("模数必须为正")
  118. log: list[str] = []
  119. begin, end = self.bounds(left, right, log)
  120. answer = 0
  121. for index in range(begin, end):
  122. segment = self.segments[index]
  123. answer = (answer + segment.length * pow(segment.value, exponent, modulus)) % modulus
  124. log.append(f"幂和只遍历了 {end - begin} 张段卡。")
  125. return answer, log
  126. def attack(self) -> list[str]:
  127. self.model = list(range(1, self.n + 1))
  128. self.segments = [Segment(index, index, value) for index, value in enumerate(self.model, start=1)]
  129. log = [
  130. f"现在每个位置值都不同:共有 {len(self.segments)} 张单点段卡。",
  131. "接下来 add(1,n,1) 仍需扫过所有段;相邻差不变,无法压缩。",
  132. ]
  133. return log
  134. def show(self) -> None:
  135. print("段卡:", " ".join(segment.show() for segment in self.segments))
  136. print("数组:", self.model)
  137. print("段数:", len(self.segments))
  138. def check(self) -> tuple[bool, str]:
  139. expanded: list[int] = []
  140. previous_right = 0
  141. for segment in self.segments:
  142. if segment.left != previous_right + 1 or segment.left > segment.right:
  143. return False, "段卡没有按顺序无缝覆盖"
  144. expanded.extend([segment.value] * segment.length)
  145. previous_right = segment.right
  146. if previous_right != self.n:
  147. return False, "段卡没有覆盖到 n"
  148. if expanded != self.model:
  149. return False, f"段卡展开 {expanded} 与数组验算 {self.model} 不同"
  150. return True, "段卡覆盖、数值与普通数组均一致"
  151. def print_log(log: list[str]) -> None:
  152. for line in log:
  153. print(" ", line)
  154. def warmup() -> None:
  155. print("=== 任务 0:先猜边界,不要先背 split ===")
  156. print("初始段卡是 [1,3,1] [4,5,4] [6,9,2]。")
  157. try:
  158. input("预测 split(2) 会把哪张卡拆成哪两张,再按回车: ")
  159. except EOFError:
  160. pass
  161. print("核对:[1,3,1] 变为 [1,1,1] 与 [2,3,1],数组数值完全不变。")
  162. def main() -> None:
  163. lab = SegmentLab()
  164. print("ODT 段卡小实验室:这里用列表展示卡片,方便看清 split。")
  165. warmup()
  166. lab.show()
  167. print("\n命令:show;split 2;add 2 7 3;assign 3 8 9;kth 1 9 5;pow 1 9 2 97;attack;check;q。")
  168. while True:
  169. try:
  170. raw = input("\n> ").strip()
  171. except EOFError:
  172. raw = "q"
  173. if not raw or raw in {"q", "quit", "exit"}:
  174. return
  175. parts = raw.split()
  176. try:
  177. if parts == ["show"]:
  178. lab.show()
  179. elif parts == ["attack"]:
  180. print_log(lab.attack())
  181. lab.show()
  182. elif parts == ["check"]:
  183. ok, message = lab.check()
  184. print("通过:" if ok else "失败:", message)
  185. elif len(parts) == 2 and parts[0] == "split":
  186. log: list[str] = []
  187. lab.split(int(parts[1]), log)
  188. print_log(log)
  189. lab.show()
  190. elif len(parts) == 4 and parts[0] == "add":
  191. print_log(lab.add(int(parts[1]), int(parts[2]), int(parts[3])))
  192. lab.show()
  193. elif len(parts) == 4 and parts[0] == "assign":
  194. print_log(lab.assign(int(parts[1]), int(parts[2]), int(parts[3])))
  195. lab.show()
  196. elif len(parts) == 4 and parts[0] == "kth":
  197. answer, log = lab.kth(int(parts[1]), int(parts[2]), int(parts[3]))
  198. print_log(log)
  199. print("答案:", answer)
  200. elif len(parts) == 5 and parts[0] == "pow":
  201. answer, log = lab.power_sum(int(parts[1]), int(parts[2]), int(parts[3]), int(parts[4]))
  202. print_log(log)
  203. print("答案:", answer)
  204. else:
  205. raise ValueError("命令格式不对")
  206. except ValueError as error:
  207. print("小提示:", error)
  208. if __name__ == "__main__":
  209. main()