📅  最后修改于: 2023-12-03 14:58:35.549000             🧑  作者: Mango
这是一道来源于 门|门 CS 1999 的算法题,需要使用动态规划的思想解决。题目给定一些钥匙和门的信息,门有开关状态,每个钥匙可以开启特定的一些门。求最少需要携带哪些钥匙才能够打开所有门。
根据题目所述,我们可以确定,这是一道典型的动态规划问题。具体来说,我们可以使用状态压缩和状压DP的思想,用一个长度为$N$ 的二进制数来表示钥匙的拥有情况。其中,$N$ 表示钥匙的种类数,为了便于处理,我们将该二进制数对应的十进制数记为 $S$。
对于每个门,设该门所需的钥匙种类为 $k$,如果该门处于开启状态,则对应一个包含 $k$ 个元素的钥匙集合 $K$;如果该门处于关闭状态,则记 $K=∅$。因此,我们需要记录两个状态,即当前所持有的钥匙集合 $T$ 和门的开关状态 $D$。因此,设 $dp(i,j,k)$ 表示在处理完前 $i$ 所有门的情况下,手上有钥匙集合为 $j$,当前门打开状态为 $k$(开门状态为 $1$,关门状态为 $0$)时,从开始到 $i$ 所有需要的钥匙集合的最小值。 $$dp(i,j,k)=\min(dp(i-1,j',k')+num(i,j))$$
其中,$j'$ 表示转移时所需携带的钥匙集合,为当前集合 $j$ 和门集合 $K$ 的并集,即 $j'=j \cup K$。$k'$ 表示此时门的开启状态,如果拥有钥匙集合 $j'$ 能够打开该门,则 $k'=1$,否则 $k'=0$。$num(i,j)$ 表示拥有钥匙集合 $j$ 能否打开当前门,如果能够打开,则 $num(i,j)=0$,否则 $num(i,j)=\infty$。
# 示例代码
n, m, s = map(int, input().split())
key = [0] * m
for i in range(m):
a, b = map(int, input().split())
for j in list(map(int, input().split())):
key[i] |= (1 << j - 1)
door = [0] * s
for i in range(s):
c, d = map(int, input().split())
door[i] = [int(i) for i in input().split()[1:]]
INF = 0x3f3f3f3f
dp = [[[INF] * 2 for j in range(1 << n)] for i in range(s + 1)]
dp[0][0][1] = 0
for i in range(1, s + 1):
for j in range(1 << n):
for k in range(2):
if dp[i - 1][j][k] != INF:
dp[i][j][1] = min(dp[i][j][1], dp[i - 1][j][k])
for x, y in enumerate(key):
if j & y:
j2 = j
else:
j2 = j | y
nk, nd = k, 0
for d in door[i - 1]:
if d == x + 1:
nd = 1
break
if nk == 1 and nd == 0:
dp[i][j2][nd] = min(dp[i][j2][nd], dp[i - 1][j][k] + 1)
else:
dp[i][j2][nd] = min(dp[i][j2][nd], dp[i - 1][j][k])
ans = dp[s][(1 << n) - 1][1]
if ans == INF:
print(-1)
else:
print(ans)
该算法充分体现出了动态规划的思想,通过建立不同的状态,记录过程中所用到的所有信息,最后得到满足条件的最优解。形式各异的动规问题具有类似的思想,熟练掌握动态规划技巧,能够使我们快速地理解和解决其他的相似问题。