P1057 传球游戏

题目描述
上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。

游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没有传出去的那个同学就是败者,要给大家表演一个节目。

聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有三个同学1号、2号、3号,并假设小蛮为1号,球传了3次回到小蛮手里的方式有1->2->3->1和1->3->2->1,共2种。

输入格式
一行,有两个用空格隔开的整数n,m(3≤n≤30,1≤m≤30)。

输出格式
1个整数,表示符合题意的方法数。

输入输出样例
输入 #1

3 3

输出 #1

2

说明/提示
40%的数据满足:3≤n≤30,1≤m≤20

100%的数据满足:3≤n≤30,1≤m≤30

2008普及组第三题

思路分析

因为该题要表示传球几次球和将球传给谁的方法总数,所以需要用两个状态量才能表达出该含义。num[i][j]代表第i次传球时将球传给同学j的方法总数。为了方便使得i+1和i-1能形成一个环,(1与n通过求余形成环不方便),可将小蛮设为0号同学。因为题目说明了从小蛮手里开始传的球。所以num[0][0](第0位(小蛮)同学将球传0次传给第0位同学的方法总数)应该为1,因为此时球本来就在他的手里,而num[0][1]…num[0][n-1]都为0,因为此时球不在他们的手里,想通过传0次球将球传到他们的手里的方法总数为0。所以该题的动态规划的初始值已经得到了,而第i次传球时将球传给同学j的方法总数等于球传了i-1次到达同学j左边那个人的手里的次数与球传i-1次到达同学j右边那个人的手里的次数之和。因为球只能从他的左边传给他或者从他的右边传给他。
即递推式为
num[i][j]=num[i-1][(j-1+n)%n]+num[i-1][(j+1)%n];
因为j-1有可能为-1(第0位同学将球传给左边的同学时的情况),而此时求出来的下标应该为n-1,可通过(j-1+n)%n求得正确的下标。

代码

#include 
using namespace std;
int main()
{
    int num[38][38]={1},n,m;//num[0][0]为1,其他元素为0,num[i][j]代表第i次传球时将球传给同学j
    cin>>n>>m;//n为人数,m为传球
    for(int i=1;i<=m;i++){
        for(int j=0;j<n;j++){
            //num[i][j]等于球传i-1次到达他左边那个人的手里的次数加球传i-1次到达他右边那个人的手里的次数
            num[i][j]=num[i-1][(j-1+n)%n]+num[i-1][(j+1)%n];
        }
    }
    cout<<num[m][0]<<endl;//球传m次到达第0位(小蛮)同学的手里
    return 0;
}

扩展知识点

  1. 一定要开num[38][38]这么大的空间吗?
    想明白了吗?小靓仔(女)!!!
    答案是不需要,其实int num[2][38]就够了,因为求球传i次到达同学j手里的方法总数只会用到球传i-1次的结果,且该问题由于无后效性,所以可以用num[2][38]来相互覆盖计算。

代码

#include 
using namespace std;
int main()
{
    int num[2][38]={1},n,m;//num[0][0]为1,其他元素为0,num[i][j]代表第i次传球时将球传给同学j
    cin>>n>>m;//n为人数,m为传球
    for(int i=1;i<=m;i++){
        for(int j=0;j<n;j++){
            //num[i][j]等于球传i-1到达他左边那个人的手里的次数加球传i-1到达他右边那个人的手里的次数
            num[i%2][j]=num[(i-1)%2][(j-1+n)%n]+num[(i-1)%2][(j+1)%n];
        }
    }
    cout<<num[m%2][0]<<endl;//球传m次到达第0位(小蛮)同学的手里
    return 0;
}

你可能感兴趣的:(动态规划)