按编号做字典序贪心,以区间调度倍增作为可扩展性判定,并用 Treap 维护空闲时间段。
OJ: luogu
题目 ID: P7562
难度:NOI/NOI+/CTSC
标签:字典序贪心倍增Treappython
日期: 2026-07-16 18:28
题意
从按编号给出的活动中选择恰好 k 个两两不冲突的活动。若可行,输出编号递增且字典序最小的方案。
思路
依次扫描活动编号。若当前活动与已选活动兼容,并且选它之后仍能补足剩余数量,就立刻选择;这是构造字典序最小序列的标准“能选则选”。难点是快速判断“还能选多少个”。
对任意空闲时间段 [left, right],最多活动数由经典区间调度贪心得到:每次选择开始不早于当前位置且结束最早的活动。离散化端点后,next_end[x] 表示这一步会到达的最早结束坐标;对它建立倍增,query(left, right) 就能在
已选活动把时间轴切成若干互不相交的空闲段,总可选数是各段 query 之和。尝试加入 [L,R] 时,只需把它所在空闲段的贡献替换为 [free_left,L] 与 [R,free_right] 两段的贡献。
空闲段需要按左端点查询前驱、删除和插入,代码用随机 Treap 维护。若新的总容量仍不少于所需活动数,就正式选择当前编号并分裂空闲段。
Python 知识
- 集合推导式收集所有端点,
sorted加bisect_left完成离散化。 array("i")保存倍增表,在n=10^5时比整数列表节省很多内存。- Treap 的左右儿子、优先级、键和值分别存在列表中,用整数编号代替大量节点对象。
- 固定
random.seed(...)使随机树形可复现;期望复杂度仍为对数级。 "\n".join(map(str, chosen))一次输出选择序列。
代码
python
import random
import sys
from array import array
from bisect import bisect_left
data = iter(map(int, sys.stdin.buffer.read().split()))
n, required = next(data), next(data)
events = [(next(data), next(data)) for _ in range(n)]
coordinates = sorted({value for event in events for value in event})
compressed = [(bisect_left(coordinates, left), bisect_left(coordinates, right))
for left, right in events]
coordinate_count = len(coordinates)
next_end = array("i", [coordinate_count]) * (coordinate_count + 1)
for left, right in compressed:
if right < next_end[left]:
next_end[left] = right
for i in range(coordinate_count - 1, -1, -1):
if next_end[i + 1] < next_end[i]:
next_end[i] = next_end[i + 1]
jump = [next_end]
for _ in range(1, n.bit_length() + 1):
previous = jump[-1]
jump.append(array("i", map(previous.__getitem__, previous)))
def query(left, right):
count = 0
current = left
for level in range(len(jump) - 1, -1, -1):
destination = jump[level][current]
if destination <= right:
current = destination
count += 1 << level
return count
# Randomized treap of disjoint free intervals, keyed by left endpoint.
random.seed(20260716)
left_child = [0]
right_child = [0]
priority = [0]
key = [0]
value = [0]
def new_node(left, right):
left_child.append(0)
right_child.append(0)
priority.append(random.randrange(1 << 30))
key.append(left)
value.append(right)
return len(key) - 1
def split(root, split_key):
if not root:
return 0, 0
if key[root] < split_key:
left_tree, right_tree = split(right_child[root], split_key)
right_child[root] = left_tree
return root, right_tree
left_tree, right_tree = split(left_child[root], split_key)
left_child[root] = right_tree
return left_tree, root
def merge(left_root, right_root):
if not left_root or not right_root:
return left_root or right_root
if priority[left_root] < priority[right_root]:
right_child[left_root] = merge(right_child[left_root], right_root)
return left_root
left_child[right_root] = merge(left_root, left_child[right_root])
return right_root
def insert(root, node):
if not root or priority[node] < priority[root]:
left_child[node], right_child[node] = split(root, key[node])
return node
if key[node] < key[root]:
left_child[root] = insert(left_child[root], node)
else:
right_child[root] = insert(right_child[root], node)
return root
def erase(root, target_key):
if key[root] == target_key:
return merge(left_child[root], right_child[root])
if target_key < key[root]:
left_child[root] = erase(left_child[root], target_key)
else:
right_child[root] = erase(right_child[root], target_key)
return root
def predecessor(root, target_key):
result = 0
while root:
if key[root] <= target_key:
result = root
root = right_child[root]
else:
root = left_child[root]
return result
root = new_node(0, coordinate_count - 1)
available = query(0, coordinate_count - 1)
chosen = []
for identity, (event_left, event_right) in enumerate(compressed, 1):
node = predecessor(root, event_left)
if not node or value[node] < event_right:
continue
free_left, free_right = key[node], value[node]
remaining = (available - query(free_left, free_right)
+ query(free_left, event_left) + query(event_right, free_right))
if remaining >= required - len(chosen) - 1:
chosen.append(identity)
available = remaining
root = erase(root, free_left)
root = insert(root, new_node(free_left, event_left))
root = insert(root, new_node(event_right, free_right))
if len(chosen) == required:
break
print("\n".join(map(str, chosen)) if len(chosen) == required else -1)复杂度
离散化、倍增和逐活动判定共
总结
字典序贪心本身只有一句“能选则选”,真正的工程核心是提供快速的可扩展性判定:区间调度倍增计算容量,Treap 维护被已选活动切开的空闲段。