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

posted @ 2024-12-10 01:33  cccpchenpi  阅读(14)  评论(0编辑  收藏  举报