洛谷P1057解答记录(dp相关)

文章介绍了如何使用动态规划解决NOIP2008普及组的传球游戏问题,详细阐述了状态转移方程和核心代码,涉及人数n和传球次数m对问题求解的影响。
摘要由CSDN通过智能技术生成

最近学校课程实验实在太多,都没什么时间写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;
}

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YUKIPEDIA~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值