[NOIP 2012 普及组] 文化之旅(疑似错题)

GitHub跳转原题关系图返回列表

把当前国家与已学文化集合共同作为 Dijkstra 状态,并用集合包含关系做支配剪枝。

OJ: luogu

题目 ID: P1078

难度:提高+/省选-

标签:状态最短路Dijkstra支配剪枝python

日期: 2026-07-16 20:10

题意

路径上不能重复学习文化,也不能进入排斥任一已学文化的国家,求起终点最短路。原题被官方标为不保证多项式可解的错题。

思路

能否继续走取决于完整历史文化集合,不能只记录国家。状态为 (node, learned_mask),在其上运行 Dijkstra;Python 整数可直接容纳 100 位文化集合。

同一节点若已有状态文化集合是当前集合的子集,且距离不大于当前距离,那么它限制更少、代价更低,当前状态被支配。插入新状态时也删除被它支配的旧状态,缓解指数状态增长。

进入邻国前检查文化是否重复,以及邻国文化的排斥掩码是否与已学集合相交。

Python 知识

  • 任意精度整数让 1 << culture 可直接表示 100 种文化。
  • heapq 处理非负边权状态最短路。
  • 列表推导式重建当前节点的非支配状态集合。
  • 元组成员测试用于跳过已被删除的堆中旧状态。

代码

python
import heapq
import sys


data = iter(map(int, sys.stdin.buffer.read().split()))
n, culture_count, edge_count, start, target = (next(data), next(data), next(data),
                                                next(data) - 1, next(data) - 1)
culture = [next(data) - 1 for _ in range(n)]
rejected = [0] * culture_count
for i in range(culture_count):
    for j in range(culture_count):
        if next(data):
            rejected[i] |= 1 << j
graph = [[] for _ in range(n)]
for _ in range(edge_count):
    u, v, weight = next(data) - 1, next(data) - 1, next(data)
    graph[u].append((v, weight))
    graph[v].append((u, weight))

best = [[] for _ in range(n)]


def add_state(node, mask, distance):
    if any(old_distance <= distance and old_mask & mask == old_mask
           for old_mask, old_distance in best[node]):
        return False
    best[node] = [(old_mask, old_distance) for old_mask, old_distance in best[node]
                  if not (distance <= old_distance and mask & old_mask == mask)]
    best[node].append((mask, distance))
    return True


start_mask = 1 << culture[start]
add_state(start, start_mask, 0)
heap = [(0, start, start_mask)]
answer = -1

while heap:
    distance, node, mask = heapq.heappop(heap)
    if (mask, distance) not in best[node]:
        continue
    if node == target:
        answer = distance
        break
    for neighbor, weight in graph[node]:
        next_culture = culture[neighbor]
        bit = 1 << next_culture
        if mask & bit or rejected[next_culture] & mask:
            continue
        next_mask = mask | bit
        next_distance = distance + weight
        if add_state(neighbor, next_mask, next_distance):
            heapq.heappush(heap, (next_distance, neighbor, next_mask))

print(answer)

复杂度

理论状态数可达 O(n2K)O(n2^K),这正是题目被标为错题的原因;支配剪枝只改善实际数据,不改变最坏指数复杂度。

总结

正文不应伪称这题存在普通最短路多项式解;历史集合必须进入状态,官方数据性质才让剪枝搜索可运行。