P1057 [NOIP2008 普及组] 传球游戏
(题源洛谷)
题目描述
上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
游戏规则是这样的:nn个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没有传出去的那个同学就是败者,要给大家表演一个节目。
聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了mm次以后,又回到小蛮手里。两种传球方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有三个同学11号、22号、33号,并假设小蛮为11号,球传了33次回到小蛮手里的方式有11->22->33->11和11->33->22->11,共22种。
输入格式
一行,有两个用空格隔开的整数n,m(3 \le n \le 30,1 \le m \le 30)n,m(3≤n≤30,1≤m≤30)。
输出格式
11个整数,表示符合题意的方法数。
输入输出样例
输入3 3 输出 2
好,这道题目其实一看思路就比较清楚,一般暴力搜索和正常递归能解决
先给个正常递归
#include"bits/stdc++.h"
using namespace std;
int n, m;
int answer = 0;//方法数
void Movetheball(int x, int ans)
{
if (ans == m)
{
if (x == 0)
{
answer++;
ans = 0;
}
return;
}
if (x - 1 >= 0 && x + 1 < n)
{
Movetheball(x + 1, ans + 1);
Movetheball(x - 1, ans + 1);
}
else if (x == n-1) {
Movetheball(0, ans + 1);
Movetheball(x - 1, ans + 1);
}
else if (x == 0) {
Movetheball(x + 1, ans + 1);
Movetheball(n-1, ans + 1);
}
}
int main()//快乐主函数
{
cin >> n >> m;
Movetheball(0, 0);
cout << answer << endl;
return 0;
}
但是我们发现时间上达不到要求,会有tle,因而我选择用动态规划。
按步骤来
一:确定状态,有n个人,那我假设小蛮是0号,于是我们就有了0到n-1的编号
一共传m次,并且要回到小蛮手上,所以我用F[0][m]来表示,经过m次传球传到0号小蛮手上的方法数,这就是最后的答案;
二:状态转移方程,我们可以预见,最后一次传球是从1号或者n-1号同学传到0号小蛮手里的
所以可以列出方程F[0][m] = F[1][m - 1] + F[n-1 ][m];
然后由于同学们围成了一个环,所以要特殊考虑头尾。
三:边界条件,这是一个关键点,其实就是F[0][0]=1,代表了一次也不传,球一开始在小蛮手里,那就只有一种。
然后看上去就可以愉快地给出答案了。
但是!这里我发现一个我曾经不怎么注意的,也就是双重循环的顺序问题。思域平时就按惯性的思维i,j谁在上谁在下没啥问题,但是显然这是需要考虑的。
先给大家看两段代码
for (int j = 1; j <= m; j++)
for(int i=0;i<n;i++)
{
if (i - 1 >= 0 && i + 1 < n)
{
F[i][j] = F[i - 1][j - 1] + F[i + 1][j - 1];
}
else if (i == n - 1) {
F[i][j] = F[i - 1][j - 1] + F[0][j - 1];
}
else if (i == 0) {
F[i][j] = F[n - 1][j - 1] + F[1][j - 1];
}
}
for(int i=0;i<n;i++)
for (int j = 1; j <= m;j++)
{
if (i - 1 >= 0 && i + 1 < n)
{
F[i][j] = F[i - 1][j - 1] + F[i + 1][j - 1];
}
else if (i == n - 1) {
F[i][j] = F[i - 1][j - 1] + F[0][j - 1];
}
else if (i == 0) {
F[i][j] = F[n - 1][j - 1] + F[i + 1][j - 1];
}
}
大家可以看一下,显然不同点在于i,j的先后问题,谁在内层谁在外层。
可是其实仔细思考一下便可知道,如果想第二段代码一样,那么显然我们需要的答案F[0][m]在等式左边只出现了一次,那就一次外层第一次循环内层第三次循环,那么显然:第一如果这样是对的,那我们要后面的循环干什么;第二,我们需要的答案应该是在最后几次或者最后一次循环中,被算出来;第二,从实际上来说,真正约束这个题目的只有两点,一个是要传回小蛮手里,第二个是只能传m次,那么第一个其实我们对我们的状态转移不产生影响,所欲第二次才是整体的约束,那它当然应该放在外层啦。
AC代码
+
#include"bits/stdc++.h"
using namespace std;
int main()
{
cin >> n >> m;
int F[31][31];
memset(F, 0, sizeof(F));
F[0][0] = 1;
for (int j = 1; j <= m; j++)
for(int i=0;i<n;i++)
{
if (i - 1 >= 0 && i + 1 < n)
{
F[i][j] = F[i - 1][j - 1] + F[i + 1][j - 1];
}
else if (i == n - 1) {
F[i][j] = F[i - 1][j - 1] + F[0][j - 1];
}
else if (i == 0) {
F[i][j] = F[n - 1][j - 1] + F[1][j - 1];
}
}
cout << F[0][m] << endl;
return 0;
}