sincerit 演算法競賽寶典 傳球遊戲
傳球遊戲 題目描述
上體育課的時候,小蠻的老師經常帶著同學們一起做遊戲。這次,老師帶著同學們一起做傳球遊戲。
遊戲規則是這樣的:n個同學站成一個圓圈,其中的一個同學手裡拿著一個球,當老師吹哨子時開始傳球,每個同學可以把球傳給自己左右的兩個同學中的一個(左右任意),當老師再次吹哨子時,傳球停止,此時,拿著球沒傳出去的那個同學就是敗者,要給大家表演一個節目。
聰明的小蠻提出一個有趣的問題:有多少種不同的傳球方法可以使得從小蠻手裡開始傳的球,傳了m次以後,又回到小蠻手裡。兩種傳球的方法被視作不同的方法,當且僅當這兩種方法中,接到球的同學按接球順序組成的序列是不同的。比如有3個同學1號、2號、3號,並假設小蠻為1號,球傳了3次回到小蠻手裡的方式有1->2->3->1和1->3->2->1,共2種。
輸入
輸入檔案ball.in共一行,有兩個用空格隔開的整數n,m(3<=n<=30,1<=m<=30)。
輸出
輸出檔案ball.out共一行,有一個整數,表示符合題意的方法數。
樣例輸入
3 3
樣例輸出
2
遞迴模擬搜尋(沒有遞推式子)只有到最後一步才知道是否找到一種解
所以用一個變數在最後一步來記錄方法數
如果有遞推式子的就不能用變數去記錄了,設定返回值就可以
比如放蘋果 solve(m,n) = solve(m-n, n) + solve(m, n-1) 只有兩部分的值加起來才能得到答案所以是有返回值的答案(個人對放蘋果和傳球遊戲的解答過程不一樣的理解)
這個解法是從模擬過程出發考慮的
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <algorithm>
using namespace std;
int N, M, sum;
void game(int n, int m) {
if (m == 0) {
if (n == 1) ++sum;
return;
}
if (n == 1) {
game(n+1, m-1);
game(N, m-1);
}
if (n == N) {
game (1, m-1);
game(n-1, m-1);
}
if (n > 1 && n < N) {
game(n+1, m-1);
game(n-1, m-1);
}
}
int main() {
while (cin >> N >> M) {
sum = 0;
game(1, M); // 規定了從1開始傳
cout << sum << "\n";
}
return 0;
}
使用動態規劃—從最後要得到的結果出發推到過程
我們發現要球傳到某個人, 只能從左邊傳過來或者從右邊傳過來,設當前球的位置用i表示,傳球的次數用j表示,那麼dp[i][j]的方案數是左右兩邊的方案數之和
即 dp[i][j] = dp[i-1][j-1] + dp[i+1][j-1]
邊界考慮:
dp[1][j] = dp[n][j-1] + dp[2][j-1]
dp[n][j] = dp[n-1][j-1] + dp[1][j-1]
初始化 dp[1][0] = 1
dp[1][0] = 1這個初始化可以參考遞迴方程的出口的含義
表示當又傳回到1號同學時並且傳球次數用完了就找到了一種傳球方法
例如: dp[2][1] = dp[1][0] + dp[3][0] = 1 + 0 = 1;
也可以看成把2位置上的求給1,3號位置的人並且次數用完一次
dp[i][j] // i號位置上的球,傳球次數剩j次的方案數
我們要求的是dp[1][m]傳球方案數
#include <stdio.h>
#include <cstring>
int dp[100][100];
// dp[i][j]當前球號在位置i,傳球次數為j時的方案數
// 轉移方程 dp[i][j] = dp[i-1][j-1] + dp[i+1][j-1];
// 邊界條件 dp[1][0] = 1;
int main() {
int n, m; // n個人傳m次
while (~scanf("%d%d", &n, &m)) {
memset(dp, 0, sizeof(dp));
int i, j;
dp[1][0] = 1; // 從1號位置開始傳球
for (i = 1; i <= m; i++) {
for (j = 2; j < n; j++)
dp[j][i] = dp[j-1][i-1] + dp[j-1][i-1];
dp[1][i] = dp[n][i-1] + dp[2][i-1];
dp[n][i] = dp[n-1][i-1] + dp[1][i-1];
}
printf("%d\n", dp[1][m]);
}
return 0;
}