| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- #!/usr/bin/env python3
- """课前零食:用可见段卡模拟 ODT 的 split 与区间操作。"""
- from __future__ import annotations
- from dataclasses import dataclass
- @dataclass
- class Segment:
- left: int
- right: int
- value: int
- @property
- def length(self) -> int:
- return self.right - self.left + 1
- def show(self) -> str:
- return f"[{self.left},{self.right},v={self.value}]"
- class SegmentLab:
- def __init__(self) -> None:
- self.model = [1, 1, 1, 4, 4, 2, 2, 2, 2]
- self.segments = [
- Segment(1, 3, 1),
- Segment(4, 5, 4),
- Segment(6, 9, 2),
- ]
- @property
- def n(self) -> int:
- return len(self.model)
- def normalize(self) -> None:
- merged: list[Segment] = []
- for segment in self.segments:
- if (
- merged
- and merged[-1].right + 1 == segment.left
- and merged[-1].value == segment.value
- ):
- merged[-1].right = segment.right
- else:
- merged.append(Segment(segment.left, segment.right, segment.value))
- self.segments = merged
- def split(self, position: int, log: list[str]) -> int:
- if not 1 <= position <= self.n + 1:
- raise ValueError("切分位置应在 1..n+1")
- if position == self.n + 1:
- log.append("split(n+1):这是尾后边界,不新建段。")
- return len(self.segments)
- for index, segment in enumerate(self.segments):
- if segment.left <= position <= segment.right:
- if position == segment.left:
- log.append(f"split({position}):刚好在 {segment.show()} 左端,什么也不切。")
- return index
- left_piece = Segment(segment.left, position - 1, segment.value)
- right_piece = Segment(position, segment.right, segment.value)
- self.segments[index : index + 1] = [left_piece, right_piece]
- log.append(
- f"split({position}):{segment.show()} 变成 "
- f"{left_piece.show()} 与 {right_piece.show()}。"
- )
- return index + 1
- raise RuntimeError("段卡没有完整覆盖数组")
- def bounds(self, left: int, right: int, log: list[str]) -> tuple[int, int]:
- if not 1 <= left <= right <= self.n:
- raise ValueError("区间应满足 1 ≤ l ≤ r ≤ n")
- end_position = right + 1
- self.split(end_position, log)
- begin = self.split(left, log)
- # C++ set 的 end 迭代器在 split(left) 后仍有效;本实验室为可读性
- # 使用列表,左侧插入可能令整数下标右移,故重新定位同一边界。
- if end_position == self.n + 1:
- end = len(self.segments)
- else:
- end = next(
- index
- for index, segment in enumerate(self.segments)
- if segment.left == end_position
- )
- return begin, end
- def add(self, left: int, right: int, delta: int) -> list[str]:
- log: list[str] = []
- begin, end = self.bounds(left, right, log)
- touched = end - begin
- for index in range(begin, end):
- self.segments[index].value += delta
- for index in range(left - 1, right):
- self.model[index] += delta
- self.normalize()
- log.append(f"区间加触及 {touched} 张完整段卡;规范化后共有 {len(self.segments)} 段。")
- return log
- def assign(self, left: int, right: int, value: int) -> list[str]:
- log: list[str] = []
- begin, end = self.bounds(left, right, log)
- removed = end - begin
- self.segments[begin:end] = [Segment(left, right, value)]
- self.model[left - 1 : right] = [value] * (right - left + 1)
- self.normalize()
- log.append(
- f"赋值删除中央 {removed} 张卡,插入一张 [{left},{right},v={value}];"
- f"规范化后共有 {len(self.segments)} 段。"
- )
- return log
- def kth(self, left: int, right: int, rank: int) -> tuple[int, list[str]]:
- log: list[str] = []
- begin, end = self.bounds(left, right, log)
- pieces = sorted(
- (self.segments[index].value, self.segments[index].length)
- for index in range(begin, end)
- )
- total = sum(length for _, length in pieces)
- if not 1 <= rank <= total:
- raise ValueError("k 超出区间长度")
- original_rank = rank
- for value, length in pieces:
- if rank <= length:
- log.append(f"按值排序后的段贡献为 {pieces};第 {original_rank} 小落在值 {value}。")
- return value, log
- rank -= length
- raise RuntimeError("长度账本损坏")
- def power_sum(self, left: int, right: int, exponent: int, modulus: int) -> tuple[int, list[str]]:
- if modulus <= 0:
- raise ValueError("模数必须为正")
- log: list[str] = []
- begin, end = self.bounds(left, right, log)
- answer = 0
- for index in range(begin, end):
- segment = self.segments[index]
- answer = (answer + segment.length * pow(segment.value, exponent, modulus)) % modulus
- log.append(f"幂和只遍历了 {end - begin} 张段卡。")
- return answer, log
- def attack(self) -> list[str]:
- self.model = list(range(1, self.n + 1))
- self.segments = [Segment(index, index, value) for index, value in enumerate(self.model, start=1)]
- log = [
- f"现在每个位置值都不同:共有 {len(self.segments)} 张单点段卡。",
- "接下来 add(1,n,1) 仍需扫过所有段;相邻差不变,无法压缩。",
- ]
- return log
- def show(self) -> None:
- print("段卡:", " ".join(segment.show() for segment in self.segments))
- print("数组:", self.model)
- print("段数:", len(self.segments))
- def check(self) -> tuple[bool, str]:
- expanded: list[int] = []
- previous_right = 0
- for segment in self.segments:
- if segment.left != previous_right + 1 or segment.left > segment.right:
- return False, "段卡没有按顺序无缝覆盖"
- expanded.extend([segment.value] * segment.length)
- previous_right = segment.right
- if previous_right != self.n:
- return False, "段卡没有覆盖到 n"
- if expanded != self.model:
- return False, f"段卡展开 {expanded} 与数组验算 {self.model} 不同"
- return True, "段卡覆盖、数值与普通数组均一致"
- def print_log(log: list[str]) -> None:
- for line in log:
- print(" ", line)
- def warmup() -> None:
- print("=== 任务 0:先猜边界,不要先背 split ===")
- print("初始段卡是 [1,3,1] [4,5,4] [6,9,2]。")
- try:
- input("预测 split(2) 会把哪张卡拆成哪两张,再按回车: ")
- except EOFError:
- pass
- print("核对:[1,3,1] 变为 [1,1,1] 与 [2,3,1],数组数值完全不变。")
- def main() -> None:
- lab = SegmentLab()
- print("ODT 段卡小实验室:这里用列表展示卡片,方便看清 split。")
- warmup()
- lab.show()
- 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。")
- while True:
- try:
- raw = input("\n> ").strip()
- except EOFError:
- raw = "q"
- if not raw or raw in {"q", "quit", "exit"}:
- return
- parts = raw.split()
- try:
- if parts == ["show"]:
- lab.show()
- elif parts == ["attack"]:
- print_log(lab.attack())
- lab.show()
- elif parts == ["check"]:
- ok, message = lab.check()
- print("通过:" if ok else "失败:", message)
- elif len(parts) == 2 and parts[0] == "split":
- log: list[str] = []
- lab.split(int(parts[1]), log)
- print_log(log)
- lab.show()
- elif len(parts) == 4 and parts[0] == "add":
- print_log(lab.add(int(parts[1]), int(parts[2]), int(parts[3])))
- lab.show()
- elif len(parts) == 4 and parts[0] == "assign":
- print_log(lab.assign(int(parts[1]), int(parts[2]), int(parts[3])))
- lab.show()
- elif len(parts) == 4 and parts[0] == "kth":
- answer, log = lab.kth(int(parts[1]), int(parts[2]), int(parts[3]))
- print_log(log)
- print("答案:", answer)
- elif len(parts) == 5 and parts[0] == "pow":
- answer, log = lab.power_sum(int(parts[1]), int(parts[2]), int(parts[3]), int(parts[4]))
- print_log(log)
- print("答案:", answer)
- else:
- raise ValueError("命令格式不对")
- except ValueError as error:
- print("小提示:", error)
- if __name__ == "__main__":
- main()
|