weekly contest 414。

題目

有個 50 x 50 的棋盤,上方有一個和一些
輸入兩個整數 kx, ky,其中 (kx, ky) 代表馬的位置。
還有二維整數陣列 positions,其中 positions[i] = [xi, yi] 代表兵的位置。

Alice 和 Bob 在玩一種回合制遊戲,由 Alice 先。每回合:

  • 玩家控制馬,並吃掉還存在棋盤上的任意一個兵,且不能繞遠路
    注意:可以選擇任何兵,不一定要選距離馬最近的兵。
  • 馬在移動的過程,可能會碰到其他兵,但他們不會被吃掉。在本回合內只有被選中的兵會被吃。

Alice 希望使得兩人的總移動次數最大化,而 Bob 則希望最小化

求兩者都選擇最佳策略的情況下,Alice 可以達到的最大總移動次數

注意:馬有 8 種移動方向,都是朝某個方向前進 1 格,然後再朝垂直的方向前進 1 格。

解法

這個馬的移動方式很麻煩,好像沒什麼公式算,只能用 bfs 暴力模擬,枚舉出發點、求出各座標所需的移動次數。

棋盤大小寬度 L = 50,每次 bfs 複雜度 O(L^2)。
有 L^2 個起點,總複雜度 O(L^4),大約 6e6 計算量,能不能過還真不好說。

但是棋盤中最多只會有 N = 15 個兵。
雖然是要由某個位置 (px, py) 走到某個兵 (x, y) 上,但反過來走也是等價的。
如此一來最多只需要 15 次 bfs,大概 3e4 計算量,看起來可行不少。

以 dist[i][px][py] 表示 positions[i] 與 (px, py) 的最短距離。


接下來的問題是,要選擇哪個兵吃?

就算兩者吃的順序不同,也可能會剩餘相同的兵存活,有重疊的子問題,因此考慮 dp。
而每個兵只能被選一次,最多也只有 15 個,可以用 bitmask 表示存活狀態,做狀態壓縮 dp

此外,我們還需要維護當前位置
當前位置只可能是起點 (kx, ky) 或是某個兵的位置,因此直接以狀態 i 表示出發點為 positions[i]。
至於起點 (kx, ky) 則直接加入 positions 中,即 positions[N]。

最後,因為兩人策略不同,轉移時分別用 max/min。
再多一個狀態 is_alice = 0/1,其中 1 代表是 Alice 的回合,要採用 max;否則採用 min。


定義 dp(i, mask):當前位於 positions[i],且還有 mask 個兵可以選。
轉移:dp(i, mask) = max(dp(j, new_mask) + dist[j][px][py])。
base:當 mask = (1 « N) - 1 時,全部兵都吃完,回傳 0。

時間複雜度 O(N * L^2 + N^2 * 2^N)。
空間複雜度 O(N * L^2 + N * 2^N)。

最初從 positions[N] = (kx, ky) 出發、所有兵都可選,mask = 0、且由 Alice 先手,is_alice = 1。
答案入口為 dp(N, 0, 1)。

DIRS = ((2, 1), (1, 2), (-1, 2), (-2, 1), (-2, -1), (-1, -2), (1, -2), (2, -1))
L = 50

class Solution:
    def maxMoves(self, kx: int, ky: int, positions: List[List[int]]) -> int:
        N = len(positions)
        positions.append([kx, ky])
        dist = [[[-1] * L for _ in range(L)] for _ in range(N)]

        def bfs(i):
            px, py = positions[i]
            q = [[px, py]]
            dist[i][px][py] = 0
            step = 1
            while q:
                q2 = []
                for x, y in q:
                    for dx, dy in DIRS:
                        xx, yy = x + dx, y + dy
                        if xx < 0 or xx >= L or yy < 0 or yy >= L or dist[i][xx][yy] != -1:
                            continue
                        q2.append([xx, yy])
                        dist[i][xx][yy] = step
                q = q2
                step += 1
            return

        for i in range(N):
            bfs(i)

        @cache
        def dp(i, mask, is_alice):
            if mask == (1 << N) - 1:
                return 0
            px, py = positions[i]
            compare = max if is_alice else min  # max for alice, min for bob
            res = -inf if is_alice else inf
            for j in range(N):
                if mask & (1 << j) != 0:
                    continue
                new_mask = mask | (1 << j)
                res = compare(
                    res, dp(j, new_mask, is_alice ^ 1) + dist[j][px][py])
            return res

        return dp(N, 0, 1)

先預處理所有移動距離,複雜度 O(L^4)。
只稍微快了一點點。

時間複雜度 O(N^2 * 2^N),預處理不計入。
空間複雜度 O(N * 2^N),預處理不計入。

DIRS = ((2, 1), (1, 2), (-1, 2), (-2, 1), (-2, -1), (-1, -2), (1, -2), (2, -1))
L = 50
dist = [[[[-1] * L for _ in range(L)] for _ in range(L)] for _ in range(L)]


def bfs(px, py):
    q = [[px, py]]
    dist[px][py][px][py] = 0
    step = 1
    while q:
        q2 = []
        for x, y in q:
            for dx, dy in DIRS:
                xx, yy = x + dx, y + dy
                if xx < 0 or xx >= L or yy < 0 or yy >= L or dist[px][py][xx][yy] != -1:
                    continue
                q2.append([xx, yy])
                dist[px][py][xx][yy] = step
        q = q2
        step += 1
    return


for px in range(L):
    for py in range(L):
        bfs(px, py)


class Solution:
    def maxMoves(self, kx: int, ky: int, positions: List[List[int]]) -> int:
        N = len(positions)
        positions.append([kx, ky])

        @ cache
        def dp(i, mask, is_alice):
            if mask == (1 << N) - 1:
                return 0
            px, py = positions[i]
            compare = max if is_alice else min  # max for alice, min for bob
            res = -inf if is_alice else inf
            for j in range(N):
                if mask & (1 << j) != 0:
                    continue
                x, y = positions[j]
                new_mask = mask | (1 << j)
                res = compare(
                    res, dp(j, new_mask, is_alice ^ 1) + dist[x][y][px][py])
            return res

        return dp(N, 0, 1)