
2025-06-23:破解锁的最少时间Ⅰ。用go语言,Bob 被困在一个地窖,必须解开 n 把锁才能逃出。每把锁需要一定的能量才能打开,这些能量值存储在数组 strength 中,其中 strength[i] 表示第 i 把锁所需的能量。
Bob 拥有一把特殊的剑,剑的状态和解锁规则如下:
现在需要计算 Bob 打开所有锁所需的最短时间(分钟数),并返回这个最少时间。
n == strength.length。
1 <= n <= 8。
1 <= K <= 10。
1 <= strength[i] <= 1000000。
输入:strength = [3,4,1], K = 1。
输出:4。
解释:
时间 | 能量 | X | 操作 | 更新后的 X |
|---|---|---|---|---|
0 | 0 | 1 | 什么也不做 | 1 |
1 | 1 | 1 | 打开第 3 把锁 | 2 |
2 | 2 | 2 | 什么也不做 | 2 |
3 | 4 | 2 | 打开第 2 把锁 | 3 |
4 | 3 | 3 | 打开第 1 把锁 | 3 |
无法用少于 4 分钟打开所有的锁,所以答案为 4 。
题目来自力扣3376。
这是一个排列和调度问题,需要找到解锁顺序的最优排列,使得总时间最短。由于 n 很小(≤8),可以尝试所有可能的解锁顺序(排列),计算每种顺序的总时间,然后取最小值。
strength 数组的所有排列,表示解锁的顺序。t=0,能量 e=0,速率 X=1。e ≥ strength[i]:t,能量为 e,速率为 X。e + X * dt ≥ strength[i],其中 dt 是等待时间。dt ≥ ceil((strength[i] - e) / X),如果 e ≥ strength[i],则 dt=0。t += dt + 1(dt 是等待时间,+1 是解锁操作的时间)。e=0,X += K。代码使用了最小费用最大流(MCMF)来解决这个问题:
ceil(strength[i] / (1 + K*j))。.
package main
import (
"fmt"
"math"
)
func findMinimumTime(strength []int, k int)int {
n := len(strength)
S := n * 2
T := S + 1
// rid 为反向边在邻接表中的下标
type neighbor struct{ to, rid, cap, cost int }
g := make([][]neighbor, T+1)
addEdge := func(from, to, cap, cost int) {
g[from] = append(g[from], neighbor{to, len(g[to]), cap, cost})
g[to] = append(g[to], neighbor{from, len(g[from]) - 1, 0, -cost})
}
for i, s := range strength {
// 枚举这个锁是第几次开的
for j := range n {
x := 1 + k*j
addEdge(i, n+j, 1, (s-1)/x+1)
}
addEdge(S, i, 1, 0)
}
for i := n; i < n*2; i++ {
addEdge(i, T, 1, 0)
}
// 下面是最小费用最大流模板
dis := make([]int, len(g))
type vi struct{ v, i int }
fa := make([]vi, len(g))
inQ := make([]bool, len(g))
spfa := func()bool {
for i := range dis {
dis[i] = math.MaxInt
}
dis[S] = 0
inQ[S] = true
q := []int{S}
forlen(q) > 0 {
v := q[0]
q = q[1:]
inQ[v] = false
for i, e := range g[v] {
if e.cap == 0 {
continue
}
w := e.to
newD := dis[v] + e.cost
if newD < dis[w] {
dis[w] = newD
fa[w] = vi{v, i}
if !inQ[w] {
inQ[w] = true
q = append(q, w)
}
}
}
}
// 循环结束后所有 inQ[v] 都为 false,无需重置
return dis[T] < math.MaxInt
}
minCost := 0
for spfa() {
// 沿 st-end 的最短路尽量增广
// 特别地,如果建图时所有边的容量都设为 1,那么 minF 必然为 1,下面第一个 for 循环可以省略
minF := math.MaxInt
for v := T; v != S; {
p := fa[v]
minF = min(minF, g[p.v][p.i].cap)
v = p.v
}
for v := T; v != S; {
p := fa[v]
e := &g[p.v][p.i]
e.cap -= minF
g[v][e.rid].cap += minF
v = p.v
}
minCost += dis[T] * minF
}
return minCost
}
func main() {
strength := []int{3, 4, 1}
K := 1
fmt.Println(findMinimumTime(strength, K))
}

.
# -*-coding:utf-8-*-
from collections import deque
import math
def find_minimum_time(strength, K):
n = len(strength)
S = n * 2
T = S + 1
# 邻接表中每条边用字典表示
# neighbor: to, rid (反向边下标), cap, cost
g = [[] for _ in range(T + 1)]
def add_edge(frm, to, cap, cost):
g[frm].append({'to': to, 'rid': len(g[to]), 'cap': cap, 'cost': cost})
g[to].append({'to': frm, 'rid': len(g[frm]) - 1, 'cap': 0, 'cost': -cost})
for i, s in enumerate(strength):
# 枚举这个锁是第几次开的
for j in range(n):
x = 1 + K * j
# 计算开锁需要的时间 (向上取整用 (s-1)//x + 1)
cost = (s - 1) // x + 1
add_edge(i, n + j, 1, cost)
add_edge(S, i, 1, 0)
for i in range(n, n * 2):
add_edge(i, T, 1, 0)
dis = [math.inf] * (T + 1)
fa = [None] * (T + 1) # 存父节点 (v, i)
in_q = [False] * (T + 1)
def spfa():
nonlocal dis, fa, in_q
for i in range(len(dis)):
dis[i] = math.inf
fa[i] = None
in_q[i] = False
dis[S] = 0
q = deque([S])
in_q[S] = True
while q:
v = q.popleft()
in_q[v] = False
for i, e in enumerate(g[v]):
if e['cap'] == 0:
continue
w = e['to']
new_dist = dis[v] + e['cost']
if new_dist < dis[w]:
dis[w] = new_dist
fa[w] = (v, i)
if not in_q[w]:
in_q[w] = True
q.append(w)
return dis[T] < math.inf
min_cost = 0
while spfa():
# 找到增广路径的最小容量
min_f = math.inf
v = T
while v != S:
pv, pi = fa[v]
min_f = min(min_f, g[pv][pi]['cap'])
v = pv
# 沿路径更新容量
v = T
while v != S:
pv, pi = fa[v]
g[pv][pi]['cap'] -= min_f
rid = g[pv][pi]['rid']
g[v][rid]['cap'] += min_f
v = pv
min_cost += dis[T] * min_f
return min_cost
if __name__ == "__main__":
strength = [3, 4, 1]
K = 1
print(find_minimum_time(strength, K))