n 個同學圍成一圈(編號 1~n,小蠻為 1 號),球從 1 號開始傳,每次只能傳給左右相鄰同學。傳 m 次後球回到 1 號手中。求不同傳球方法數。兩種方法不同 ⇔ 接球序列不同。
3 ≤ n ≤ 30, 1 ≤ m ≤ 30輸入: 3 3
輸出: 2
解釋: 1→2→3→1 和 1→3→2→1
dp[i][j]:第 j 次傳球結束後,球恰好在 i 號手中的方法數
1 ≤ i ≤ n, 0 ≤ j ≤ mdp[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-1right(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];
#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;
}
第 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';
狀態轉移可寫為矩陣乘法 DP_j = T × DP_{j-1},其中 T 為 n×n 轉移矩陣(三對角循環矩陣)。答案為 (T^m)[1][1]。對 m ≤ 30 當前 DP 更優;若 m ≤ 10^9 則需矩陣快速冪。
| n\m | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| 3 | 0 | 2 | 2 | 6 | 10 | 22 |
| 4 | 0 | 2 | 0 | 6 | 0 | 20 |
| 5 | 0 | 2 | 0 | 6 | 0 | 22 |
觀察:m 為奇數時 n=4 結果為 0(奇數步無法從 1 回到 1,奇偶性約束)。
dp[1][0]=1,其餘 dp[i][0]=0。別寫成 dp[2][1]=1, dp[n][1]=1 雖然結果同但語義不清i==1 i==n 易出錯)n,m ≤ 30 最大答案 < 2^30,int 足夠,但習慣用 long long 防溢出while(cin >> n >> m) 正確讀取至 EOFdp[i][j] = Σ p[k] * dp[neighbor_k][j-1]核心洞見: 環狀結構 DP 的關鍵在於**統一的索引映射**(模運算)而非分支枚舉。將「物理鄰居關係」抽象為「索引數學關係」,是解決環/環面/周期性邊界問題的通用技巧。