蓝桥杯印章

更多相似题目
蓝桥杯算法题解

1. 蓝桥杯印章

问题描述

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

输入格式

一行两个正整数n和m

输出格式

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

样例输入

2 3

样例输出

0.7500

数据要求

m,n <=30

题解

一看数据量就猜测是dp的问题,数学问题的数据量一般很大。如果想到了dp,那么就好办了。

  1. 确定维度和含义,题目就两个数字,直接两维(i, j);含义就按照题目 or 子问题的思路去思考。
    • (i, j)表示买了i枚印章,有j种的概率。
  2. 确定子问题,也就是递推式,也就是划分。这里根据最后一次买的印章种类进行划分。
    1. 如果最后一次买的是新种的印章, f(i, j) = f(i - 1, j - 1) * (n - (j - 1) / n)
    2. 如果最后一次买的是旧种类的印章, f(i, j - 1) = f(i - 1, j - 1) * ((j - 1) / n)
    3. 上面的式子2需要进行变形,因为我需要求解的是f(i, j), 那么f(i, j) = (i - 1, j) * (j / n)。
  3. 可以看到上面的划分是不重不漏的,最后的f(i, j)就是式1和2的和。相比于直接想到,f(i - 1, j)这里使用背包dp的思维更加容易。
  4. 最后确定边界。f(1, 1) = 1, 其他等于0。表示买一张有一种的概率是1。

代码

  • 使用滚动数组优化
package lanqiao.imporve;

import java.util.Arrays;
import java.util.Scanner;

/**
 * @author: Zekun Fu
 * @date: 2022/11/21 10:38
 * @Description: 印章
 *
 * 1. dp[n][m]:表示买n枚,集齐m种印章的概率
 *
 * 当前这一种是一种新印章的概率 (n - j) / n
 * 当前这一种是一种旧印章的概率 (j) / n
 *
 * 1. dp[i][j - 1] = dp[i - 1][j - 1] * ((j - 1) / n)
 * -> dp[i][j] = dp[i - 1][j] * (j / n)
 *
 *  2. dp[i][j] = dp[i - 1][j - 1] * ((n - j + 1) / n)
 *
 *  综上
 *  dp[i][j] = dp[i - 1][j] * (j / n) + dp[i - 1][j - 1] * ((n - j + 1) / n)
 *
 *  每一种印章有两种选择
 *  1. 抽到新的
 *  2. 抽到旧的
 *
 * 可以大体看作是背包模型。
 *
 * 2 3的时候
 *
 * i == 1的时候
 * 0 : 0
 * 1 : 1
 *
 * i == 2的时候
 * 1 : 1 * 0.5
 * 2 : 0.5
 *
 * i 等于3的时候
 * 1 = 1
 * 2 = 0.5 + 0.5 * 0.5 = 0.75
 *
 *
 */
public class YinZhang {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(), m = sc.nextInt();
        double[] dp = new double[n + 1];
        Arrays.fill(dp, 0);
        dp[1] = 1;
        double d = (double)n;
        for (int i = 2; i <= m; i++) {
            double[] pre = Arrays.copyOf(dp, n + 1);
            for (int j = 1; j <= Math.min(n, i); j++) {
                dp[j] = pre[j - 1] * ((double) (n - j + 1) / d) + pre[j] * ((double)j / d);
            }
        }
        System.out.printf("%.4f", dp[n]);
    }
}

cpp代码

  • 没有优化的代码
#include 
#include 

using namespace std;


// m种印章种
/*
    f[i][j]:表示从i种印章中恰好选择j种印章的概率
    f[m][n] = f[m - 1][n - 1]

    1. i种印章中含有j种印章的第一种划分
        当前选择的这个是已经选择过的印章
        f[i - 1][j] * j/n
    2. 当前选择的是一种新的印章
        f[ - 1][j - 1] * (n - j + 1) / n;

    3. 边界条件
        i = 1的时候,f[1][j] = 1 / n;
        i < j的时候,等于0


*/

const int N =1005;
double f[N][N];

int main() {
    int m, n;
    cin >> n >> m;

    f[1][1] = 1.0;
    for (int i = 2; i <= m; i++) {
        for (int j = 1; j <= i; j++) {
            f[i][j] = f[i - 1][j - 1] * (double)(n - j + 1) / n + f[i - 1][j] * (double) j / n;
            //cout << f[i - 1][j] * (double) j / n << endl;
        }
    }

    printf("%.4llf\n", f[m][n]);
}

  • 使用滚动数组优化
#include 
#include 
#include 

using namespace std;


// m种印章种
/*
    f[i][j]:表示从i种印章中恰好选择j种印章的概率
    f[m][n] = f[m - 1][n - 1]

    1. i种印章中含有j种印章的第一种划分
        当前选择的这个是已经选择过的印章
        f[i - 1][j] * j/n
    2. 当前选择的是一种新的印章
        f[ - 1][j - 1] * (n - j + 1) / n;

    3. 边界条件
        i = 1的时候,f[1][j] = 1;
        i < j的时候,等于0


*/

const int N = 25;
double f[2][N];

int main() {
    int m, n;
    cin >> n >> m;

    f[1][1] = 1.0;
    for (int i = 2; i <= m; i++) {
        for (int j = 1; j <= i; j++) {
            f[i & 1][j] = f[(i - 1) & 1][j - 1] * (double)(n - j + 1) / n + f[(i - 1) & 1][j] * (double) j / n;
            //cout << f[i - 1][j] * (double) j / n << endl;
        }
    }

    printf("%.4llf\n", f[m & 1][n]);
}

你可能感兴趣的:(算法,#,蓝桥杯,蓝桥杯,算法)