CF618F Double Knapsack 题解(最短代码)与推广
题意:给定两个长为 \(n\) 的 \([1, n]\) 之间的数组,对两者分别给出一个子序列,使得两个子序列的和相等。
在洛谷看到了一篇讲的不错的题解,然而误打误撞发现了一个更美的思路,所以过来写一下。
令初始和为 \(0\)。使用某种 DP 中经典的震荡方式取数,也就是当 \(s \ge 0\) 时,从 B 中取一个数,并令 \(s\) 减去这个数;否则从 A 中取,并令 \(s\) 加上这个数。那么显然有:
-
若在两个不同的状态 \(s\) 相同,那么这两个状态取数之差就是答案。
-
\(-n \le s < n\) 始终成立,这是由震荡的取数过程保证的。
假设某个序列取完了都没有答案。
-
若两个序列都取完了,那么总共有 \(2n + 1\) 个状态,而 \(s\) 的取值只有 \(2n\) 种,不符合鸽巢原理。
-
若 B 取完了仍没有答案,那么说明 \(s \ge 0\) 的状态出现了 \(n + 1\) 次(B 取完了,且现在还应该取 B),然而 \(s \ge 0\) 只有 \(n\) 种取值,即 \([0, n - 1]\)。
-
A 取完的情况完全对称,\(s\) 的取值只有 \([-n, -1]\) 这 \(n\) 种。
所以代码出奇的简单,轻而易举一发拿下 shortest(截至 2024.12.10)。
n = int(input())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
s, i, j = 0, 0, 0
mp = {s: (i, j)}
while i < n or j < n:
if s >= 0:
s -= b[j]
j += 1
else:
s += a[i]
i += 1
if s in mp:
i0, j0 = mp[s]
print(i - i0)
print(*range(i0 + 1, i + 1))
print(j - j0)
print(*range(j0 + 1, j + 1))
break
mp[s] = (i, j)
推广:若有长 \(n\) 的 \([1, m]\) 序列和长 \(m\) 的 \([1, n]\) 序列,有相同结论。
证明见 https://artofproblemsolving.com/community/c7h2319310p18494593