CF618F Double Knapsack 題解(最短程式碼)

cccpchenpi發表於2024-12-10

題意:給定兩個長為 \(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)

相關文章