题解:洛谷 P1057 传球游戏
【题目来源】
洛谷:P1057 [NOIP 2008 普及组] 传球游戏 - 洛谷
【题目描述】
上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。
游戏规则是这样的:\(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\) 个整数,表示符合题意的方法数。
【输入样例】
3 3
【输出样例】
2
【核心思想】
-
问题分析:\(n\) 个同学站成一个圆圈,小蛮(编号 \(1\))开始传球,每次可以传给左边或右边的相邻同学,传 \(m\) 次后求球回到小蛮手里的方案数。这是一个环形线性DP问题,关键在于用DP记录每一步到达每个位置的方案数,并用模运算处理环形结构。
-
算法选择:
- 线性DP:
f[i][j]表示传 \(i\) 次后到达第 \(j\) 个同学的方案数 - 环形处理:使用模运算
(j - 1 + n) % n和(j + 1) % n处理环形相邻关系 - 状态转移:当前位置的方案数由上一轮左右相邻位置的方案数之和得到
- 线性DP:
-
关键步骤:
- 初始化:
f[0][0] = 1,表示传 \(0\) 次时球在小蛮(编号 \(0\))手里,方案数为 \(1\) - 状态转移:对于每一步 \(i\)(\(1 \leq i \leq m\))和每个位置 \(j\)(\(0 \leq j < n\))
f[i][j] = f[i-1][(j-1+n)%n] + f[i-1][(j+1)%n]- 即当前位置 \(j\) 的方案数等于上一轮左边和右边相邻位置的方案数之和
- 答案获取:
f[m][0],即传 \(m\) 次后回到小蛮(编号 \(0\))手里的方案数
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times m)\),外层 \(m\) 步,内层 \(n\) 个位置
- 空间复杂度:\(O(n \times m)\),二维DP数组(可优化为 \(O(n)\))
-
环形线性DP的核心思想:
- 状态设计:
f[i][j]表示经过 \(i\) 步到达位置 \(j\) 的方案数 - 环形处理:使用模运算将环形结构转化为线性结构处理,编号 \(0\) 和 \(n-1\) 相邻
- 无后效性:当前状态只与上一轮状态有关,满足DP最优子结构
- 方案数DP:适用于"从起点出发,走 \(m\) 步回到起点的路径数"类问题
- 可空间优化:因为只依赖上一轮状态,可用滚动数组将空间优化至 \(O(n)\)
- 状态设计:
【算法标签】
普及 #线性DP-一维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 35; // 定义最大节点数
int n; // 节点总数(环形排列)
int k; // 移动步数
int f[N][N]; // DP数组,f[i][j]表示移动i步后到达j位置的方案数
int main()
{
// 输入节点数和移动步数
cin >> n >> k;
// 初始化:0步时在0位置的方案数为1
f[0][0] = 1;
// 动态规划计算
for (int i = 1; i <= k; i++) // 遍历每一步
{
for (int j = 0; j < n; j++) // 遍历每个位置
{
// 状态转移:当前步数i到达j的方案数等于
// 上一步i-1在j-1位置的方案数加上i-1在j+1位置的方案数
// 使用模运算处理环形结构
f[i][j] = f[i - 1][(j - 1 + n) % n] + f[i - 1][(j + 1) % n];
}
}
// 输出移动k步后回到原点的方案数
cout << f[k][0] << endl;
return 0;
}
【运行结果】
3 3
2
浙公网安备 33010602011771号