首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

動態規劃經典:圓圈傳球游戲 — 狀態轉移與邊界處理全解析

     編輯:關於C語言

問題描述

n 個同學圍成一圈(編號 1~n,小蠻為 1 號),球從 1 號開始傳,每次只能傳給左右相鄰同學。傳 m 次後球回到 1 號手中。求不同傳球方法數。兩種方法不同 ⇔ 接球序列不同。

約束條件

  • 3 ≤ n ≤ 30, 1 ≤ m ≤ 30
  • 多組測試用例,EOF 結束

樣例

輸入: 3 3
輸出: 2
解釋: 1→2→3→1 和 1→3→2→1

動態規劃建模

狀態定義

dp[i][j]:第 j 次傳球結束後,球恰好在 i 號手中的方法數

  • 1 ≤ i ≤ n, 0 ≤ j ≤ m
  • 初始狀態:dp[1][0] = 1(0 次傳球,球在 1 號)

狀態轉移方程

第 j 次傳球到 i,必來自第 j-1 次在左右鄰居:

dp[i][j] = dp[left(i)][j-1] + dp[right(i)][j-1]

其中:

  • left(i) = (i == 1) ? n : i-1
  • right(i) = (i == n) ? 1 : i+1

邊界處理(環狀結構關鍵)

// 通用寫法:模運算統一處理環狀索引
int left = (i - 2 + n) % n + 1;  // 1-based 索引左鄰居
int right = i % n + 1;           // 1-based 索引右鄰居
dp[i][j] = dp[left][j-1] + dp[right][j-1];

完整代碼實現(C++17)

#include 
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    while (cin >> n >> m) {
        // dp[i][j]: 第 j 次傳球後球在 i 號手中的方案數
        vector> dp(n + 1, vector(m + 1, 0));
        dp[1][0] = 1; // 初始:0 次傳球,球在 1 號

        for (int j = 1; j <= m; ++j) {
            for (int i = 1; i <= n; ++i) {
                int left = (i - 2 + n) % n + 1;
                int right = i % n + 1;
                dp[i][j] = dp[left][j-1] + dp[right][j-1];
            }
        }
        cout << dp[1][m] << '\n';
    }
    return 0;
}

空間優化:滾動數組 O(n)

第 j 步只依賴 j-1 步,可壓縮為兩個一維數組:

vector prev(n+1), curr(n+1);
prev[1] = 1;
for (int step = 1; step <= m; ++step) {
    fill(curr.begin(), curr.end(), 0);
    for (int i = 1; i <= n; ++i) {
        int left = (i - 2 + n) % n + 1;
        int right = i % n + 1;
        curr[i] = prev[left] + prev[right];
    }
    swap(prev, curr);
}
cout << prev[1] << '\n';

數學視角:矩陣快速冪 O(n³ log m)

狀態轉移可寫為矩陣乘法 DP_j = T × DP_{j-1},其中 T 為 n×n 轉移矩陣(三對角循環矩陣)。答案為 (T^m)[1][1]。對 m ≤ 30 當前 DP 更優;若 m ≤ 10^9 則需矩陣快速冪。

結果驗證與規律探索

n\m123456
302261022
40206020
50206022

觀察:m 為奇數時 n=4 結果為 0(奇數步無法從 1 回到 1,奇偶性約束)。

避坑指南

  1. 初始條件:dp[1][0]=1,其餘 dp[i][0]=0。別寫成 dp[2][1]=1, dp[n][1]=1 雖然結果同但語義不清
  2. 環狀索引:務必用模運算統一處理,避免分支漏寫(如原代碼分開寫 i==1 i==n 易出錯)
  3. 數據類型:n,m ≤ 30 最大答案 < 2^30,int 足夠,但習慣用 long long 防溢出
  4. 多用例處理:while(cin >> n >> m) 正確讀取至 EOF

舉一反三:相關變體題

  • 不同起點/終點:修改初始狀態與最終查詢位置
  • 可傳給任意 k 步內同學:轉移方程求和範圍擴大
  • 帶權重/概率:dp[i][j] = Σ p[k] * dp[neighbor_k][j-1]
  • 環上隨機游走期望步數:馬爾可夫鏈吸收狀態分析

核心洞見: 環狀結構 DP 的關鍵在於**統一的索引映射**(模運算)而非分支枚舉。將「物理鄰居關係」抽象為「索引數學關係」,是解決環/環面/周期性邊界問題的通用技巧。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved