N×M 棋盤,國王從 (x,y) 起始,恰好走 K 步 (每步可走周圍 8 格或原地?不,國王必須移動到相鄰 8 格),不走出棋盤,最終回到起點的路徑數。模 9999991。
國王移動可分解為:
定義 dp[step][pos] = 走 step 步後在相對位置 pos 的方案數。
位置編碼:pos = offset + k,offset = K,範圍 [-K, K]。
// 水平 DP (垂直同理)
dp[0][offset + x] = 1; // 起始位置 x (1-based)
for (int s = 1; s <= K; ++s) {
for (int p = -K; p <= K; ++p) {
int idx = offset + p;
if (dp[s-1][idx] == 0) continue;
// 三種移動:左(-1)、不動(0)、右(+1) -- 但國王必須移動!
// 實際上國王水平分量可為 -1,0,1,垂直分量同理
// 這裡計算單軸獨立移動,後續組合時排除 (0,0)
if (p > 1) dp[s][idx-1] = (dp[s][idx-1] + dp[s-1][idx]) % MOD; // 左移
if (p < M) dp[s][idx+1] = (dp[s][idx+1] + dp[s-1][idx]) % MOD; // 右移
// 注意邊界:p=1 不能左移,p=M 不能右移
}
}
棋盤大小限制:水平位置 ∈ [1, M],垂直位置 ∈ [1, N]。
DP 陣列大小:[K+1][2K+3] 覆蓋所有可能相對位移。
設 h[i] = 水平走 i 步不出界的方案數 (所有合法終點位置之和)
v[j] = 垂直走 j 步不出界的方案數
總方案數:
ans = Σ(i=0..K) h[i] * v[K-i] * C(K, i) (mod MOD)
為什麼乘 C(K,i)? K 步中選 i 步作水平移動 (其餘 K-i 步作垂直),步驟順序不同視為不同路徑。
#include#include #include using namespace std; typedef long long ll; const ll MOD = 9999991; const int MAXK = 1005; int n, m, k, x, y; ll h[MAXK], v[MAXK]; ll dp[2][2*MAXK]; // 滾動陣列優化空間 ll C[MAXK][MAXK]; void computeAxis(ll *res, int startPos, int limit, int K) { // res[i] = 走 i 步不出界方案數 memset(dp, 0, sizeof(dp)); int offset = K; dp[0][offset + startPos] = 1; res[0] = 1; for (int s = 1; s <= K; ++s) { int cur = s & 1, pre = 1 - cur; memset(dp[cur], 0, sizeof(dp[cur])); int minPos = max(1, startPos - s); int maxPos = min(limit, startPos + s); for (int p = minPos; p <= maxPos; ++p) { int idx = offset + p; ll ways = 0; // 從左、右、原地 (但國王必須移動!) // 此處計算單軸投影移動:每步可 -1, 0, +1 if (p > 1) ways = (ways + dp[pre][idx-1]) % MOD; if (p < limit) ways = (ways + dp[pre][idx+1]) % MOD; // 注意:單軸投影允許「水平不動」(0),對應垂直移動 ways = (ways + dp[pre][idx]) % MOD; // 水平分量為 0 dp[cur][idx] = ways; res[s] = (res[s] + ways) % MOD; } } } int main() { // 預計算組合數 for (int i = 0; i < MAXK; ++i) { C[i][0] = C[i][i] = 1; for (int j = 1; j < i; ++j) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % MOD; } int T; scanf("%d", &T); for (int cas = 1; cas <= T; ++cas) { scanf("%d%d%d%d%d", &n, &m, &k, &x, &y); memset(h, 0, sizeof(h)); memset(v, 0, sizeof(v)); computeAxis(h, y, m, k); // 水平:起始 y,限制 m computeAxis(v, x, n, k); // 垂直:起始 x,限制 n ll ans = 0; for (int i = 0; i <= k; ++i) { ans = (ans + h[i] * v[k-i] % MOD * C[k][i]) % MOD; } printf("Case #%d:\n%lld\n", cas, ans); } return 0; }
原始碼中 cat 函數實現細節:
g[i][j] 臨時 DP 陣列,j 為偏移索引up = k - (x-1), down = k + (limit-x) 計算合法位移範圍j-1, j-2, j+1, j+2 對應國王 8 方向移動在單軸的投影 (-2 到 +2?)修正理解:國王單步移動 8 方向,水平分量 ∈ {-1,0,1},垂直分量 ∈ {-1,0,1},但 (0,0) 非法。
單軸投影 DP 應允許步長 -1,0,1,但組合時需確保不全為 (0,0)。
原始碼中 j-2, j+2 似乎有誤,正確應為 j-1, j, j+1。
| 階段 | 複雜度 |
|---|---|
| 組合數預計算 | O(K²) |
| 單軸 DP (2次) | O(K × 範圍) ≈ O(K × min(K, N)) |
| 最終合併 | O(K) |
| 總計 | O(K² + K·min(K,N)),K≤1000 極快 |
HDU 4832 巧妙利用**維度分解 + 組合數學**。將 2D 國王移動分解為獨立的 1D 水平/垂直隨機漫步,再用組合數合併步驟順序。核心技巧:單軸 DP 計算不出界方案數、組合數分配步驟順序。注意邊界條件處理與模運算。此思想廣泛適用於網格圖受限隨機漫步計數問題。