最近学校课程实验实在太多,都没什么时间写oj。。orz
题目链接:P1057 [NOIP2008 普及组] 传球游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
本题显然涉及到动态规划,至于洛谷题解有不少提到搜索解法,有兴趣的读者自行研究,本文章仅介绍dp解法。
首先我们分析该问题:n个人围成一圈,从有球的人开始,进行m次传球,求最终将球传到开始的人手上的方案数目。现在问题涉及到两个变量:人数n和传球次数m,根据dp的解题步骤,我们需要将原始问题分解成子问题。
我们把开始有球的人编号为1,按照顺时针或逆时针方向按顺序编号一直到n。我们想要求出从1号开始,进行m次传球后球能回到他手上的方案数,就从结果开始考虑:1号是如何得到球的?显然,1号只能从2号或者n号手里拿到球,而2号只能从1号和3号手里拿到球,n号只能从1号和n - 1号手里拿到球。因此,i 号能否拿到球只和与 i 号相邻的两个人有关。
因为有m次传球,所以需要求1号进行m次传球后球能回到他手上的方案数,只需要求经过m-1次传球后,球到达2号手里的方案数和球到达n号手里的方案数即可。
状态转移方程如下
dp[i][j]表示编号为 i 的人经过 j 次传球后到他手上的方案数。此处需要注意该状态转移方程需要分两种情况:(1).和1号相邻的n号需要单独拿出来处理;(2).和1号相邻的2号可以和其他编号的人一样进行统一处理。(至于为什么,自己想想就明白了)。
最终我们再考虑遍历顺序,我这里采用的是外层遍历传球次数,内层遍历人员。核心代码如下
for (int j = 1; j <= m; j++) { // j代表传球次数为j
for (int i = 1; i <= n; i++) { // i代表第i号人
if (j == 1) {
dp[2][1] = dp[n][1] = 1;
break;
} else {
// 对于起点以及与起点相邻的点
if (i == 1 || i == n) {
if (i == 1) {
dp[i][j] = dp[2][j - 1] + dp[n][j - 1];
} else {
dp[i][j] = dp[1][j - 1] + dp[n - 1][j - 1];
}
} else { // 对于其他的点
dp[i][j] = dp[i - 1][j - 1] + dp[i + 1][j - 1];
}
}
}
}
AC代码如下
#include <iostream>
#include <vector>
using namespace std;
int n, m; // n位同学,m次传球
// 以第一个持球的同学为起点,编号为1,一直编号到n
// dp[i][j]表示编号为i的人经过j次传球到手上的方案数
int dp[35][35];
int main() {
cin >> n >> m;
for (int j = 1; j <= m; j++) { // j代表传球次数为j
for (int i = 1; i <= n; i++) { // i代表第i号人
if (j == 1) {
dp[2][1] = dp[n][1] = 1;
break;
} else {
// 对于起点以及与起点相邻的点
if (i == 1 || i == n) {
if (i == 1) {
dp[i][j] = dp[2][j - 1] + dp[n][j - 1];
} else {
dp[i][j] = dp[1][j - 1] + dp[n - 1][j - 1];
}
} else { // 对于其他的点
dp[i][j] = dp[i - 1][j - 1] + dp[i + 1][j - 1];
}
}
}
}
cout << dp[1][m] << endl;
//system("pause");
return 0;
}