传球游戏 题目描述
上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
游戏规则是这样的: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;
}