题目重点:总和守恒、前缀单调与有限状态BFS;给小规模精确答案,标明题目缺规模时不能承诺高效OA解。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 整数prices,k/d正整数,严格差值<d即整数宽度≤d−1。操作没有额外价格下限,原文未给数值范围/不可行返回政策。
- 训练不可行返回−1;示例是小规模精确求解器,不宣称达到原OA hard的时间限制,原规模/隐藏约束仍待核。
- x=y不改变状态,可删去最优路径中的此类操作;不要先排序prices,排序会改变哪些值能向哪些下标转移。
解题路线
- 每次转移保总和S。定义前缀A_r=前r个值之和,x<y操作只把A_x至A_(y−1)全部增加p,其余不变。因此所有前缀只能增加,后面低价无法向前面高价反方向取钱。
- 设平均S/n、允许整数宽w=d−1。任何终态的每个值都在L=ceil(S/n)−w到U=floor(S/n)+w之间:最大值≥ceil均值、最小值≤floor均值,再结合宽度界。
- 任一终态第r前缀≤B_r=min(rU,S−(n−r)L)。若初始A_r>B_r即可证明不可行;所有能完成的路径途中也不能越B_r,因为前缀之后不会下降。仅满足这些上界不自动证明可行,仍要搜索或更强判定。
- 用前缀tuple表示状态,最终值由相邻前缀差恢复。搜索区域A_r初始≤state_r≤B_r,每维是有限整数区间;枚举x<y和允许p,把一段前缀增加p且不越界。
- 所有操作代价1,BFS第一次满足max−min<d时层数就是最少操作。完整性:任意成功原路径的每个状态均在这个区域;无效越界路径永远回不来,剪去不丢答案。区域耗尽则不可行。
- 常见无方向版本把全局min抬高、max压低,但这里必须核对min/max位置;也不能只用ceil(需搬总量/k)作为总操作数,多个起终下标、不同边流量拆分可能多付次数。当前精确状态搜索可用于反证贪心,较大n还需正式规模和进一步优化。
手动走一遍
prices=[0,10],k3,d1,目标必须[5,5],两步3与2即可;[10,0]同参数不可行,首前缀10只能增加却终态需5。[0,1],d1因总和不能均分整数也不可行。504组n≤3小输入与独立坐标状态BFS对照,另三例方向/整除核验通过。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
"""Exact finite BFS for small integer instances, NOT an efficient large OA solver."""
from collections import deque
def minimum_operations(prices, k, d):
if not prices or k <= 0 or d <= 0:
raise ValueError("nonempty integer prices, positive k and d required")
if any(not isinstance(x, int) for x in [*prices, k, d]):
raise ValueError("integer contract required")
n, total = len(prices), sum(prices)
lower = -((-total) // n) - (d - 1) # ceil(total/n) - allowed width
upper = total // n + d - 1
prefix = []
running = 0
for value in prices[:-1]:
running += value
prefix.append(running)
initial = tuple(prefix)
bounds = tuple(min(r * upper, total - (n - r) * lower) for r in range(1, n))
if any(a > b for a, b in zip(initial, bounds)):
return -1
def goal(state):
coordinates = (0, *state, total)
values = [b - a for a, b in zip(coordinates, coordinates[1:])]
return max(values) - min(values) < d
todo, seen = deque([(initial, 0)]), {initial}
while todo:
state, depth = todo.popleft()
if goal(state):
return depth
# Transfer from price[y] to price[x], x < y:
# increments exactly prefixes x through y-1.
for x in range(n - 1):
slack = k
for y in range(x + 1, n):
slack = min(slack, bounds[y - 1] - state[y - 1])
for amount in range(1, slack + 1):
changed = list(state)
for index in range(x, y):
changed[index] += amount
next_state = tuple(changed)
if next_state not in seen:
seen.add(next_state)
todo.append((next_state, depth + 1))
return -1
复杂度
V≤∏_{r=1}^{n−1}(B_r−A_r+1);代码每状态至多O(n²k)转移,各构造/散列O(n),总O(Vn³k)时间、O(Vn)空间,是指数/伪多项式小规模方法。n或数值大时不可用;不把它描述成大规模OA标准解。
易错点与追问
容易失分的边界
- 把x≤y忘掉或擅自排序数组。
- 目标<d写成≤d。
- 默认所有输入都有解。
- 用总搬运量除k直接证明最优。
- 小规模BFS测试通过就宣称隐藏hard全过。
继续深挖
- 若原题实际允许任意x/y,算法会怎样变化?
- 有价格≥0约束时如何限制转移?
- 已给目标向量时,前缀条件能否构造可达转移?
- 得到正式n/k/price范围后怎样缩小目标区间和最优代价搜索?
