题目:
上体育课时,墨老师经常带着同学们一起做游戏。这次,墨老师带着同学们一起做传球游戏,游戏规则是这样的:个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时拿着球没传出去的那个同学就是败者,要给大家表演一个节目。
聪明的张琪曼提出一个有趣的问题:有多少种不同的传球方法可以使得从张琪曼手里开始传的球,传了M次以后,又回到张琪曼手里。两种传球的方法被称作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有3个同学1号、2号、3号,并假设张琪曼为1号,球传了3次回到张琪曼手里的方式有1→2→3→1和1→3→2→1,共两种。
输入
有两个用空格隔开的整数N,M(3≤N≤30,1≤M≤30)。
输出
有一个整数,表示符合题目的方法数。
样例输入 复制
3 3
样例输出 复制
2
解决方案
递归+剪纸+记忆化搜索
暴力搜索(时间复杂度过高):
最开始的想法,直接暴力搜索,但是超时严重,只过了不到一半的点。
#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
void f(int d,int x){
if(x < min(d, n - d)){
return ;
}
if(x == 0){
if(d == 0){
ans ++;
}
return ;
}
f((d + 1) % n, x - 1) ;
f((d + n - 1) % n, x - 1);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin >> n >> m;
f(0, m);
cout << ans << '\n';
return 0;
}
优化后
#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
int p[35][35];//将递归过程存储下来
int f(int d,int x){
if(p[d][x] != -1){
return p[d][x];
}
if(x < min(d, n - d)){//在剩余传球次数不足以传回给张琪曼时进行剪纸
return 0;
}
if(x == 0){// 当传球次数 x 为 0 时停止,判断此时球是否在张琪曼手中
if(d == 0){
return 1;
}
return 0;
}
p[d][x] = f((d + 1) % n, x - 1) + f((d + n - 1) % n, x - 1); // 将向左穿和向右传加起来
return p[d][x];
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
memset(p,-1,sizeof p);
cin >> n >> m;
ans = f(0,m);
cout << ans << '\n';
return 0;
}
动态规划
用空间换时间。
问题类型
在第 j 个位置的球只能由 j - 1 或 j + 1 的位置传过来,问题说要方案数,那么第 j 个位置时,已经传了m次的方案数 = 在 j - 1 的位置和 j + 1 的位置已经传了 m - 1 次的方案数之和。于是
定义状态
dp[i][j] 表示在传球 i 次后,在位置 j 的方案数。
转移方程
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1]
注意
在传球队伍头部位置和传球队伍尾部位置需要特殊处理
代码
#include<bits/stdc++.h>
using namespace std;
int dp[35][35];
int main(){
int n,m;cin >> n >> m;
dp[0][1] = 1;// 初始化
for(int i = 1;i <= m;i ++){
dp[i][1] = dp[i - 1][n] + dp[i - 1][2]; // 头部特殊处理
for(int j = 2;j <= n - 1;j ++){
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
}
dp[i][n] = dp[i - 1][n - 1] + dp[i - 1][1]; // 尾部特殊处理
}
cout << dp[m][1] << '\n'; // 传球 m 次后在第一个位置的方案数
return 0;
}
结束!