蒜头君的新游戏

工作空闲之余,蒜头君经常带着同事们做游戏,最近蒜头君发明了一个好玩的新游戏:n 位同事围成一个圈,同事 A 手里拿着一个兔妮妮的娃娃。蒜头君喊游戏开始,每位手里拿着娃娃的同事可以选择将娃娃传给左边或者右边的同学,当蒜头君喊游戏结束时,停止传娃娃。此时手里拿着娃娃的同事即是败者。

玩了几轮之后,蒜头君想到一个问题:有多少种不同的方法,使得从同事 A 开始传娃娃,传了 m次之后又回到了同事 A 手里。两种方法,如果接娃娃的同事不同,或者接娃娃的顺序不同均视为不同的方法。例如 1>2>3>1 和 1>3>2>1 是两种不同的方法。

输入格式

输入一行,输入两个整数n,m(3n30,1m30),表示一共有n 位同事一起游戏,一共传m 次娃娃。

输出格式

输出一行,输出一个整数,表示一共有多少种不同的传娃娃方法。

样例输入
3 3
样例输出
2

写这第一篇博客主要是因为在刚刚开始的学习动态规划的路上就撞了个满头包。

这道题理解了以后发现还是比较简单的,但是没有理解之前,特别是思路错误之后就会一步一步走进死胡同出不来。

首先写一下自己的错误思路:

把动态规划的阶段分为人数和次数,尝试去找(n, m)和(n - 1, m)以及(n, m - 1)的关系,甚至最后不死心去找和(n - 2,m - 2)的关系。错误的点是在于并没有把阶段分解成可推导的,而是武断的通过题目无脑分阶段:n个人,m次,那就建一个[n][m]的二维数组嘛,一个人传一次肯定是0,那就试着找出他们之间的关系就好了。然而如果分解成的各个阶段不能推导的话,那动态规划相比于枚举法就没什么优势了,甚至还做不出来。

正确的思路:

不要去想什么最后要的结果是什么,而是去分析这个过程,把n个人分别标号成1,2,3,4......n,如果最后的结果是要得到m次之后传到标号为1(即初始位置)的手里的话,那m - 1次肯定是在2号或者n号手中,这样状态转移方程就可以很简单写出来了:

f[i][j] = f[i - 1][j - 1] + f[i - 1][j + 1] 

*i表示的传递的次数,j表示的最后传递到的标号数,j号的左右两边的邻居分别是j + 1号和j - 1号。

但是要注意的是考虑到同事要围坐成环,即1号的邻居是2号和n号,n号的邻居是n - 1号和1号。

边界值就是传一次传到各个标号的方案数:f[1][2] = 1, f[1][n] = ,其余都是0,因为1次根本传不到。

通过1次的就可以通过状态转移方程推导出2次的情况,然后推导出3次直到m次。

Java代码如下:

public class ThrowBall {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt(), m = in.nextInt();
in.close();

int[][] f = new int[m + 1][n + 1];

f[1][2] = 1;
f[1][n] = 1;
//从第二次开始计算
for(int i = 2; i <= m; i++){  
            for(int j = 1; j <= n; j++){  
                if(j == 1)
                f[i][j] = f[i-1][2] + f[i-1][n];  
                else if(j == n)
                f[i][j] = f[i-1][1] + f[i-1][n-1];  
                else 
                f[i][j] = f[i-1][j-1] + f[i-1][j+1];  
            }  
        }
System.out.println(f[m][1]);
}
}


<think>好的,我需要回答用户关于“蒜头回家”问题的动态规划实现方法。首先,我得先理解这个问题。根据引用[1],题目是说蒜头在左下角(1,1),家在右上角(n,n),每走一个格子会消耗体力a_ij,只能向右或向上走,要求找到回家花费的最少体力。 用户提到动态规划,所以我要回忆动态规划的基本概念。引用[3]指出动态规划用于最优解问题,这题显然是最小体力路径,符合条件。动态规划通常需要定义状态和状态转移方程。 接下来,我需要确定状态。这里应该是到达每个点(i,j)所需的最小体力,用dp[i][j]表示。初始状态是起点(1,1),即dp[1][1]=a_11,因为站在起点就已经消耗这里的体力。 然后考虑状态转移。因为只能向右或向上走,所以到达(i,j)的前一步只能是左边(i-1,j)或下边(i,j-1)。因此,状态转移方程应该是dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + a_ij。但要注意边界情况,比如第一行和第一列,只能从一个方向来。 比如,第一行的每个点只能从左边来,所以dp[1][j] = dp[1][j-1] + a_1j。同样,第一列的每个点只能从下面来,dp[i][1] = dp[i-1][1] + a_i1。 然后,遍历顺序应该从起点开始,按行或列逐步填充dp表,直到终点(n,n)。 不过,我需要检查是否正确。比如,假设有一个2x2的网格,初始体力a11=1,a12=2,a21=3,a22=1。那么路径有两种:右→上或上→右。第一种的体力是1+2+1=4,第二种是1+3+1=5,所以最优是4。用动态规划的话,dp[2][2] = min(dp[2][1], dp[1][2}) + a22 = min(4,3) +1=4。对,结果正确。 另外,需要注意数组的索引是否正确,题目中的坐标是否从1开始,代码实现时要对应好数组下标,可能需要处理边界条件。 可能还需要初始化dp数组,除了起点外,其他置初始化为一个很大的值,或者逐步计算。或者直接按顺序填充,因为每个点只依赖左边和上边的点,所以按行或列的顺序填充即可。 总结步骤: 1. 定义dp数组,大小为n x n。 2. 初始化dp[0][0] = a[0][0](如果编程语言数组从0开始的话)。 3. 填充第一行和第一列。 4. 按行或列填充其他置,取左边和上边的最小值加上当前格子的体力。 5. 最终dp[n-1][n-1]即为答案。 然后根据引用[1]中的问题描述,确保实现时符合题目要求,比如坐标从(1,1)到(n,n),可能需要调整数组索引。例如,如果编程中使用0-based索引,则需要转换。 另外,要考虑空间优化,如果不需要保存整个dp表,可以用滚动数组,但可能对于初学者来说,先展示标准的二维DP更清晰。 最后,生成相关问题,可能需要结合动态规划和BFS方法的不同,或者优化技巧,比如空间优化或其他变种问题。</think>### 蒜头回家问题的动态规划实现方法 #### 问题分析 蒜头需要从网格左下角$(1,1)$移动到右上角$(n,n)$,每次只能向右或向上移动。每个格子$(i,j)$会消耗体力$a_{ij}$,目标是找到一条路径使得总体力消耗最小。 #### 动态规划设计 1. **状态定义** 定义$dp[i][j]$表示从起点$(1,1)$走到置$(i,j)$所需的最小体力值。 2. **状态转移方程** - 对于第一行$(i=1)$:只能从左边移动而来,因此 $$dp[1][j] = dp[1][j-1] + a_{1j} \quad (j \geq 2)$$ - 对于第一列$(j=1)$:只能从下方移动而来,因此 $$dp[i][1] = dp[i-1][1] + a_{i1} \quad (i \geq 2)$$ - 其他置$(i \geq 2, j \geq 2)$: $$dp[i][j] = \min(dp[i-1][j], dp[i][j-1]) + a_{ij}$$ 3. **初始化** - 起点:$dp[1][1] = a_{11}$(初始置直接消耗体力)[^1] #### 代码实现(Python示例) ```python def min_energy_path(grid): n = len(grid) dp = [[0] * n for _ in range(n)] dp[0][0] = grid[0][0] # 起点初始化(假设输入是0-based索引) # 填充第一行和第一列 for i in range(1, n): dp[i][0] = dp[i-1][0] + grid[i][0] for j in range(1, n): dp[0][j] = dp[0][j-1] + grid[0][j] # 填充其他置 for i in range(1, n): for j in range(1, n): dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j] return dp[n-1][n-1] # 返回终点值 # 示例输入(假设grid是n×n的二维数组,0-based索引) grid = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ] print(min_energy_path(grid)) # 输出最小体力值 ``` #### 关键点说明 - **时间复杂度**:$O(n^2)$,需遍历所有网格点[^3]。 - **空间优化**:若允许覆盖原数组,可将空间复杂度从$O(n^2)$优化至$O(n)$。 - **边界处理**:若题目坐标从$(1,1)$开始(如引用[1]的描述),需将输入转换为0-based索引。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值