算法训练 印章(动态规划,java实现)

文章介绍了如何用动态规划求解购买m张印章集齐n种图案的概率,涉及状态定义、状态转移方程和代码实现。
摘要由CSDN通过智能技术生成

问题描述

共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。

输入格式

一行两个正整数n和m

输出格式

一个实数P表示答案,保留4位小数。

样例输入

2 3

样例输出

0.7500

数据规模和约定

1≤n,m≤20

解题思路

本题使用动态规划算法,动态规划无非是以下三步,

  • 确认状态,即dp数组中的值的含义
  • 设计状态转移方程(递推公式)。
  • 设置dp数组的初值

其中前2步尤为关键。除此之外,对于同一个问题使用用动态规划算法,可以设计出不同的状态和对应的状态转移方程(即dp数组所代表的含义和递推公式不唯一)

首先,题目说买m个印章,凑齐n种不同的印章。有2个变量,自然的想到使用二维数组(一般的题目都是用二维数组),而题目是求买m个印章,集齐n种印章的概率。所以可以用dp[i][j]代表买i个印章,集齐j种印章的概率

情况一:

如果只有一种印章(即n=1),那么无论购买多少张印章的概率都应该为1;

情况二:

如果购买的印章数量小于印章种类数量(即n>m),那么概率应该为0

这里可以画表更加好分析:

情况三:(这里开始回到动态规划上,建议自己手写分析)

当j=1(当前收集的图案数量=1),表示小A手里有i张印章且收集到了一种图案的概率,也就是说需要小A手里的这i张印章全部都是一个类型,此时概率dp[i][j] = n *(1/n)^i = (1/n)^(i-1) (建议自己从i = 0张开始分析)

接下来分析下当我们买了i张印章时要集齐j种是不是要分情况:

  • 买到重复的时候
  • 没有买到重复的时候
  • 当买到重复的时候是不是意味着我还要抽到之前抽到过的某一种印章?
  1. 买到重复图案:
    此时我已经有了j种图案,那么此时我购买到重复的概率是j/n;此时我在和买i-1张集齐j种的概率相乘才意味着我在买i张的时候集齐了j种图案的概率,即dp[i][j]=d[i-1][j] *(j/n)
  2. 没有买到重复图案:
    即此时我买i-1张的时候有了j-1种印章,我要在买i张的时候买到不是重复的概率是(n-(j-1))/n,在与之前买i-1张集齐j种的概率相乘,才能意味着我在买i张的时候集齐了j种图案,即dp[i][j]=d[i-1][j-1]
    *(n-(i-1)/n)
  3. 最后:
    把两者相加,便是买到第i张集齐第j种的概率,即dp[i][j]=d[i-1][j]
    *(j/n) + d[i-1][j-1] *(n-(i-1)/n)

实现代码:

注意题目要求小数,记得把int类型转化为double,以及使用(1.0/n)这个小技巧;

因为需要用到i-1并且for循环从q开始的,然后在设置概率数组的时候就+1,即new double[m+1][n+1]

import java.util.Scanner;
/**
 * 动态规划问题,设dp[i][j]为购买i张印章时集齐j种印章的概率
 * 其中,如果只有一种印章(即n=1),那么无论购买多少张印章的概率都应该为1;
 * 如果购买的印章数量小于印章种类数量(即n>m),那么概率应该为0
 */

public class Main {
    public static void main(String args[]) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int m = input.nextInt();
        double[][] dp = new double[m+1][n+1];
        /**
         * 如果只有一种印章(即n=1)
         */
        if(n == 1) {
            dp[m][n] = 1;
            System.out.printf("%.4f", dp[m][n]);
            return;
        }
        /**
         *购买的印章数量小于印章种类数量
         */
        if(n > 1 && m < n) {
            dp[m][n] = 0;
            System.out.printf("%.4f", dp[m][n]);
            return;
        }

        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= n; j++) {
                if (i < j) {
                    dp[i][j] = 0;
                }
                if (j == 1) {
                    if(i==1){  //这一个if判断也可以转化为最后总结的公式
															//dp[i][j] = Math.pow((1.0/n), (i-1));
                        dp[i][j]=1;
                    }else {
                        dp[i][j] = dp[i-1][j]*(1.0/n);
                    }
                } else  {
                    dp[i][j] = dp[i-1][j] * (j*1.0 / n) + dp[i-1][j-1] * ((n - j +1)*1.0 / n);
                }
            }
        }
        System.out.printf("%.4f", dp[m][n]);
    }
}
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值