Bob 的生存概率问题

作者:Grey

原文地址:

博客园:Bob 的生存概率问题

CSDN:Bob 的生存概率问题

题目描述

给定五个参数 n , m , i , j , k,表示在一个 n*m 的区域,Bob 处在 (i,j) 点,每次 Bob 等概率的向上、 下、左、右四个方向移动一步,Bob 必须走 k 步。如果走完之后,Bob 还停留在这个区域上, 就算 Bob 存活,否则就算 Bob 死亡。请求解 Bob 的生存概率,返回字符串表示分数的方式。

题目链接:牛客-Bob的生存概率

暴力解法

由于 Bob 可以向四个方向任意一个方向走 k 步,所以,Bob 可以选择走的路线总数是:4^k,即:4 的 k 次方。

接下来就是要求在 4 ^ k 总数中,哪些是存活下来的路线,定义如下递归函数

long process(int i, int j, int k, int n, int m)

递归含义表示:目前在 (i,j) 位置,还有 k 步要走,走完了如果还在棋盘中就获得1个生存点,返回总的生存点数。

接下来是 base case,如果越界了,直接返回 0,

        if (i < 0 || i == n || j < 0 || j == m) {
return 0;
}

表示没有生存机会,

如果没有越界,但是此时正好 k == 0,说明已经有一种存活路线了,返回 1,表示一种有效路线。

        if (i < 0 || i == n || j < 0 || j == m) {
return 0;
}
// 没有越界,说明还在棋盘中,没有步数了,直接返回一种有效路线。
if (k == 0) {
return 1;
}

接下来是普遍情况, Bob 在棋盘中,可以往四面八方走,即

        long up = process(i - 1, j, k - 1, n, m);
long down = process(i + 1, j, k - 1, n, m);
long left = process(i, j - 1, k - 1, n, m);
long right = process(i, j + 1, k - 1, n, m);

上述表示四面八方走返回的有效路线,四个方向的有效路线之和,就是答案,即

return up + down + left + right;

递归函数的完整代码如下

    public static long process(int i, int j, int k, int n, int m) {
if (i < 0 || i == n || j < 0 || j == m) {
return 0;
}
// 还在棋盘中!
if (k == 0) {
return 1;
}
// 还在棋盘中!还有步数要走
long up = process(i - 1, j, k - 1, n, m);
long down = process(i + 1, j, k - 1, n, m);
long left = process(i, j - 1, k - 1, n, m);
long right = process(i, j + 1, k - 1, n, m);
return up + down + left + right;
}

由于最后的结果要返回最简的分数形式,所以假设有效路线是 X 种,所有可能的走法是 Y 种,那么返回的字符串是如下形式

return (X/gcd(X,Y)) + "/" + (Y/gcd(X,Y))

其中 gcd(X,Y) 就是利用辗转相除法得到 X,Y 的最大公约数

    public static long gcd(long m, long n) {
return n == 0 ? m : gcd(n, m % n);
}

暴力解法的完整代码如下

import java.util.Scanner;

public class Main {

    public static String livePossibility1(int i, int j, int k, int n, int m) {
return buildExp(process(i, j, k, n, m), (long) Math.pow(4, k));
} // 目前在i,j位置,还有k步要走,走完了如果还在棋盘中就获得1个生存点,返回总的生存点数
public static long process(int i, int j, int k, int n, int m) {
if (i < 0 || i == n || j < 0 || j == m) {
return 0;
}
// 还在棋盘中!
if (k == 0) {
return 1;
}
// 还在棋盘中!还有步数要走
long up = process(i - 1, j, k - 1, n, m);
long down = process(i + 1, j, k - 1, n, m);
long left = process(i, j - 1, k - 1, n, m);
long right = process(i, j + 1, k - 1, n, m);
return up + down + left + right;
} public static String buildExp(long m, long n) {
return m / gcd(m, n) + "/" + n / gcd(m, n);
} public static long gcd(long m, long n) {
return n == 0 ? m : gcd(n, m % n);
} public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int i = sc.nextInt();
int j = sc.nextInt();
int k = sc.nextInt();
System.out.println(livePossibility1(i, j, k, n, m));
sc.close();
}
}

超时

动态规划解 (可 AC)

根据上述暴力递归过程可知,递归函数有三个可变参数:i,j,k;所以,定义一个三维数组 dp,就可以把所有递归过程的中间值存下,根据 i,j,k 的可变范围,定义如下三维数组:

long[][][] dp = new long[n][m][k + 1];

根据暴力递归过程的 base case,可以初始化 dp 的某些位置的值

        long[][][] dp = new long[n][m][k + 1];
for (int row = 0; row < n; row++) {
for (int col = 0; col < m; col++) {
dp[row][col][0] = 1;
}
}

接下来是普遍情况,通过暴力递归过程可知,dp[i][j][k]依赖以下四个位置的值

dp[i-1][j][k-1]

dp[i+1][j][k-1]

dp[i][j-1][k-1]

dp[i][j+1][k-1]

即:三维数组的每一层只依赖上一层的数据结果,而第一层的值已经初始化好了,所以可以根据第一层求第二层,依次求到最后一层,这个动态规划的思路类似:象棋中的马跳步问题,不赘述。

动态规划的解完整代码如下

import java.util.Scanner;

public class Main {

    public static String livePossibility2(int i, int j, int k, int n, int m) {
long[][][] dp = new long[n][m][k + 1];
for (int row = 0; row < n; row++) {
for (int col = 0; col < m; col++) {
dp[row][col][0] = 1;
}
}
for (int rest = 1; rest <= k; rest++) {
for (int r = 0; r < n; r++) {
for (int c = 0; c < m; c++) {
dp[r][c][rest] = pick(dp, n, m, r - 1, c, rest - 1);
dp[r][c][rest] += pick(dp, n, m, r + 1, c, rest - 1);
dp[r][c][rest] += pick(dp, n, m, r, c - 1, rest - 1);
dp[r][c][rest] += pick(dp, n, m, r, c + 1, rest - 1);
}
}
}
return buildExp(dp[i][j][k], (long) Math.pow(4, k));
} public static String buildExp(long m, long n) {
return m / gcd(m, n) + "/" + n / gcd(m, n);
} public static long gcd(long m, long n) {
return n == 0 ? m : gcd(n, m % n);
} public static long pick(long[][][] dp, int n, int m, int r, int c, int rest) {
if (r < 0 || r == n || c < 0 || c == m) {
return 0;
}
return dp[r][c][rest];
} public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
int i = sc.nextInt();
int j = sc.nextInt();
int k = sc.nextInt();
System.out.println(livePossibility2(i, j, k, n, m));
sc.close();
}
}

更多

算法和数据结构笔记

Bob 的生存概率问题的更多相关文章

  1. 生存模型(Survival Model)介绍

    https://www.cnblogs.com/BinbinChen/p/3416972.html 生存分析,维基上的解释是: 生存分析(Survival analysis)是指根据试验或调查得到的数 ...

  2. R语言学习 - 非参数法生存分析--转载

    生存分析指根据试验或调查得到的数据对生物或人的生存时间进行分析和推断,研究生存时间和结局与众多影响因素间关系及其程度大小的方法,也称生存率分析或存活率分析.常用于肿瘤等疾病的标志物筛选.疗效及预后的考 ...

  3. 生存分析(survival analysis)

    一.生存分析(survival analysis)的定义 生存分析:对一个或多个非负随机变量进行统计推断,研究生存现象和响应时间数据及其统计规律的一门学科. 生存分析:既考虑结果又考虑生存时间的一种统 ...

  4. ACM里的期望和概率问题 从入门到精通

    起因:在2020年一场HDU多校赛上.有这么一题没做出来. 题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6829 题目大意:有三个人,他们分别有X,Y ...

  5. R数据分析:生存分析与有竞争事件的生存分析的做法和解释

    今天被粉丝发的文章给难住了,又偷偷去学习了一下竞争风险模型,想起之前写的关于竞争风险模型的做法,真的都是皮毛哟,大家见笑了.想着就顺便把所有的生存分析的知识和R语言的做法和论文报告方法都给大家梳理一遍 ...

  6. 转:遗传算法解决TSP问题

    1.编码 这篇文章中遗传算法对TSP问题的解空间编码是十进制编码.如果有十个城市,编码可以如下: 0 1 2 3 4 5 6 7 8 9 这条编码代表着一条路径,先经过0,再经过1,依次下去. 2.选 ...

  7. frequentism-and-bayesianism-chs-ii

    frequentism-and-bayesianism-chs-ii 频率主义 vs 贝叶斯主义 II:当结果不同时   这个notebook出自Pythonic Perambulations的博文  ...

  8. tyvj1519博彩游戏

    博彩游戏 From admin 背景 Background Bob最近迷上了一个博彩游戏…… 描述 Description 这个游戏的规则是这样的:每花一块钱可以得到一个随机数R,花上N块钱就可以得到 ...

  9. tyvj P1519 博彩游戏(AC自动机+DP滚动数组)

    P1519 博彩游戏 背景 Bob最近迷上了一个博彩游戏…… 描述 这个游戏的规则是这样的:每花一块钱可以得到一个随机数R,花上N块钱就可以得到一个随机序列:有M个序列,如果某个序列是产生的随机序列的 ...

随机推荐

  1. 除了Synchronized关键字还有什么可以保证线程安全?

    除了Synchronized关键字还有什么可以保证线程安全?     日常使用Java开发时,多线程开发,一般就用Synchronized保证线程安全,防止并发出现的错误和异常,那么 除了Synchr ...

  2. Word 分页符怎么使用

    当一页内容输入完之后,还留有很多空白区域没有填写,一直按回车键跳转到下一页显得复杂,并且回车键经过的地方都是段落. 可以手动添加分页符,使当前页跳转到下一页. 也可以使用快捷键Ctrl + Enter ...

  3. 未关中断情况下的hardlock

    最近遇到一例crash,3.10内核,hardlock,查看对应的堆栈,中断是使能的. 查看对应的hrtimer_interrupts和hrtimer_interrupt_save的值,发现确实相等. ...

  4. python与pycharm的安装与“试用”

    python与pycharm的安装与"试用" 一.python解释器安装与启动 python解释器的安装 1.打开文件安装包运行页面 #python3.8 2.选择Customiz ...

  5. zkw线段树——简单易懂好写好调的线段树

    0.简介 zkw线段树是一种非递归线段树,与普通线段树不同的是,它是棵标准的满二叉树,所以遍历过程可全程使用位运算,常数一般比线段树小得多. 1.结构/建树 前面说了,zkw线段树是满二叉树,可是原数 ...

  6. 《Java编程思想》读书笔记(五)

    前言:本文是<Java编程思想>读书笔记系列的最后一章,本章的内容很多,需要细读慢慢去理解,文中的示例最好在自己电脑上多运行几次,相关示例完整代码放在码云上了,码云地址:https://g ...

  7. 本地 maven + scala 跑spark wordcount

    pom.xml 点击查看代码 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http: ...

  8. 读完 RocketMQ 源码,我学会了如何优雅的创建线程

    RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时.高可靠的消息发布与订阅服务. 这篇文章,笔者整理了 RocketMQ 源码中创建线程的几点技巧,希望大家读完之后,能 ...

  9. 001从零开始入门Entity Framework Core——基础知识

    Entity Framework (EF) Core 是轻量化.可扩展.开源和跨平台版的常用 Entity Framework 数据访问技术. 一.什么是 Entity Framework Core ...

  10. 快速排序C语言版图文详解

    ​ 算法原理:选一个数位基准,将序列分成两个部分,一边全是比它小序列,另一边全是比它大序列.然后再分别对比他小的序列和比再次进行基准分割.依次分割下去,得到一个有序的队列. 原理图示: ​编辑 ​编辑 ...