【马蹄集】—— 概率论专题

news2024/11/19 2:38:29

概率论专题



目录

  • MT2226 抽奖概率
  • MT2227 饿饿!饭饭!
  • MT2228 甜甜花的研究
  • MT2229 赌石
  • MT2230 square




MT2226 抽奖概率

难度:黄金    时间限制:1秒    占用内存:128M
题目描述

小码哥正在进行抽奖,箱子里有一红一白两小球,每次摸出一个球,摸到红球中奖, 如果中奖,就不再继续抽奖;如果未中奖 (摸到白球),则往箱子里补充一个白球 (摸出的白球不放回),继续抽奖,直到中奖,或者达到最大抽奖次数。
假设至多能抽奖 M M M 次,求当停止抽奖时,(中奖球数/摸出总球数) 的期望。

格式

输入格式:一行,一个整数 M M M
输出格式:保留到小数后六位。

样例 1

输入:
4

输出:
0.682292

备注

其中: 1 ≤ M ≤ 10000 1\le M\le10000 1M10000


相关知识点:概率论


题解


箱子中只有一个红球和一个白球,因此第一次抽取时,抽到红球和抽到白球的概率各占一半。接下来对该箱子进行盲抽,如果抽出红色则停止抽奖;如果抽到白色则重新放入一个白球(已被抽出的白球不放回)。这就是说,对该箱子的每一次抽取,其内部总是含有一个红球和一个白球。因此这个模型实际上是一个 p = 1 2 p=\frac{1}{2} p=21 的伯努利概型。本题要求的,是停止抽奖时 “中奖球数/摸出总球数” 的数学期望。从理论上说,“抽到奖时摸出的总球数” 可以取到无穷大(即永远抽不到)。因此,本题限制假设最多能抽 M M M 次。

注意到题目要求的是 “中奖球数/摸出总球数” 的期望,而在停止抽奖时(即抽到奖或达到最大抽奖次数时),“中奖球数” 始终为1。因此我们的主要目标是算出停止抽奖时其当前 “摸出的总球数”。显然,这个值可取 1、2、……、M。具体来说,这些取值与其对应的概率如下:

  • 抽 1 次中奖的概率: 1 2 \frac{1}{2} 21(第一次摸到红球),中奖球数/摸出总球数= 1 1 \frac{1}{1} 11 =1;
  • 抽 2 次中奖的概率: 1 2 × 1 2 = 1 2 2 \frac{1}{2}\times\frac{1}{2}=\frac{1}{2^2} 21×21=221(第一次摸到白球,第二次摸到红球),中奖球数/摸出总球数 = 1 2 \frac{1}{2} 21
  • 抽 3 次中奖的概率: 1 2 2 × 1 2 = 1 2 3 \frac{1}{2^2}\times\frac{1}{2}=\frac{1}{2^3} 221×21=231(前两次摸到白球,第三次摸到红球),中奖球数/摸出总球数 = 1 3 \frac{1}{3} 31
    ……
  • M M M 次中奖的概率: 1 2 M − 1 × 1 2 = 1 2 M \frac{1}{2^{M-1}}\times\frac{1}{2}=\frac{1}{2^M} 2M11×21=2M1(前 M − 1 M-1 M1 次摸到白球,第三次摸到红球),中奖球数/摸出总球数 = 1 M \frac{1}{M} M1

我们知道,离散变量的期望公式为:

E ( X ) = ∑ x x ⋅ P ( X = x ) E\left(X\right)=\sum_xx·P(X=x) E(X)=xxP(X=x)

其中, P ( X = x ) P\left(X=x\right) P(X=x) 表示变量取 x x x 时的概率。在本题中, x x x 的取值范围即为 1 1 \frac{1}{1} 11 1 2 \frac{1}{2} 21、……、 1 M \frac{1}{M} M1,其对应的 P ( X = x ) = 1 2 x P\left(X=x\right)=\frac{1}{2^x} P(X=x)=2x1。因此本题所求期望的取值即为:

E ( X ) = ∑ x x ⋅ P ( X = x ) = 1 1 × 1 2 + 1 2 × 1 2 2 + ⋯ + 1 M × 1 2 M E\left(X\right)=\sum_xx·P(X=x)=\frac11×\frac12+\frac12×\frac{1}{2^2}+⋯+\frac1M×\frac{1}{2^M} E(X)=xxP(X=x)=11×21+21×221++M1×2M1

基于此,可直接写出求解本题的完整代码:

/*
    MT2226 抽奖概率 
    离散变量的期望公式
*/
#include<bits/stdc++.h> 
using namespace std; 
// 求“中奖球数 / 摸出总球数” 的期望
float getException(int m){
    int i = 1;
    float ans = 0;
    while(i<=m){
        ans += 1.0/(i*pow(2,i));
        i++;
    }
    return ans;
}
int main()
{
// 获取输入
    int m; cin>>m;
    // 格式化输出期望 
    printf("%.6f",getException(m));
    return 0;
} 


<br>

---

<br>

># MT2214 元素共鸣
>
><h6>难度:黄金 &emsp;&emsp; 时间限制:1秒 &emsp;&emsp; 占用内存:128M</h6>
>
><h6>题目描述</h6>
>
>遥远的大陆上存在着元素共鸣的机制。
建立一个一维坐标系,其中只有素数对应的点的坐标存在着元素力,而相距为 $k$ 的两个元素力之间能形成元素共鸣。现在,需要求出 $n$ 范围内所有能元素共鸣的点对,并将他们以第一个点的坐标大小为关键字排序后输出(小的在前)。
>
><h6>格式</h6>
>
>输入格式:一行两个整数 $n,k$。
>输出格式:所有小于等于 $n$ 的素数对。每对素数对输出一行,中间用单个空格隔开。
&emsp;&emsp;&emsp;&emsp;&emsp;若没有找到任何素数对,输出empty。
>
><h6>样例 1</h6>
>
>**输入:**
>
>6924 809
>
>**输出:**
>
>2 811
>
><h6>备注</h6>
>
>其中:$1\le k\le n\le{10}^4$。
>
><br>
>
>**相关知识点:**`筛法`


<br>

<h1 align="center">题解</h1>

<br>

**方法一:暴力求解**

本题实际就是求 “具有指定差距 $k$ 的质数对”,在 ${10}^4$ 范围下如果按暴力方式逐个求解,也是可以通过的:

```cpp
/*
    MT2214 元素共鸣 
    暴力枚举
*/
#include<bits/stdc++.h> 
using namespace std;

// 判断一个数是否为质数 
bool isPrime(int n)
{
    int limit = sqrt(n);
    for(int i=2; i<=limit; i++)
        if(n%i == 0)
            return false;
    return true;
} 

int main( )
{
    // 获取输入 
    int n, k, tmp; cin>>n>>k;
    // 查找产生共鸣的元素 
    bool flag = false;
    for(int i=2; i<n; i++) {
        // 如果当前数为质数,则进一步判断目标数是否也为质数 
        if(isPrime(i)){
            // 目标数 
            tmp = i + k;
            // 不能超过数据范围,且必须是质数 
            if(tmp<=n && isPrime(tmp)){
                flag = true;
                cout<<i<<" "<<tmp<<endl;
            }
        }
    }
    if(!flag) cout<<"empty"<<endl;
    return 0;
} 

方法二:筛法求解

也可以利用筛法,提前算出所有的质数,然后再在质数里枚举能产生共鸣的元素:

/*
    MT2214 元素共鸣 
    欧拉筛法
*/
#include<bits/stdc++.h> 
using namespace std;

const int MAX = 1e4+5;
bool vis[MAX];
int prime[MAX], cnt; 

int getPrime(int n)
{
    for(int i=2; i<=n; i++){
        if(!vis[i]) prime[cnt++] = i;
        for(int j = 0; prime[j]*i<=n; j++){
            vis[prime[j]*i] = true;
            if(i % prime[j] == 0) break;
        } 
    }
    return cnt;
}

int main( )
{
    // 获取输入 
    int n, k, tmp; cin>>n>>k;
    // 获取全部的质数
    getPrime(n);
    // 查找产生共鸣的元素 
    bool flag = false;
    for(int i=0; i<cnt-1; i++) {
        // 目标数 
        tmp = prime[i] + k;
        // 不能超过数据范围,且必须是质数 
        if(tmp<=n && !vis[tmp]){
            flag = true;
            cout<<prime[i]<<" "<<tmp<<endl;
        }
    }
    if(!flag) cout<<"empty"<<endl;
    return 0;
} 


MT2227 饿饿!饭饭!

难度:黄金    时间限制:1秒    占用内存:128M
题目描述

嗯哼,小码哥在新的一年里不会忘记身为干饭人的初心!众所周知,小码哥非常不喜欢一直吃同样的东西,但由于理想与现实的差距,食堂在这 n n n 天里只会供应种 k k k 餐食。
在一天吃 3 餐的情况下,前 w w w 天一共 w × 3 w\times3 w×3 顿饭小码哥不希望有任何一顿重复。现在请问食堂有多少种方案可以满足超级可爱乖巧的小码哥的需要。

格式

输入格式:一行,三个整数 n ,   k ,   w n,\ k,\ w n, k, w 表示 n n n 天内食堂只会供应 k k k 种餐食, w w w 的意义详见题面。
输出格式:输出一行一个数,表示满足小码哥需要的方案数。

样例 1

输入:
5 4 1

输出:
24

备注

其中: 1 ≤ n ,   w ,   k ≤ 12 1\le n,\ w,\ k\le12 1n, w, k12


相关知识点:排列组合


题解


对题目的简化描述如下:食堂提供 n n n 种餐食,小码哥要吃 m m m 顿饭,求能使小码哥不吃重的进餐方案数。

这显然是一个排列问题。例如,当 n = 4 ,   m = 3 n=4,\ m=3 n=4, m=3 时,为使小码哥不吃重,其选择餐食的思路如下:

  • 第一顿:小码哥可任意选择餐食,共 4 种方案;
  • 第二顿:小码哥只能在剩下 3 种餐食中选择;
  • 第三顿:小码哥只能在剩下 2 种餐食中选择。

于是可得到小码哥不吃重的进餐方案数为:4×3×2=24 种。

下面给出排列数的计算公式:

A n m = n × ( n − 1 ) × … × ( n − m + 1 ) = n ! ( n − m ) ! A_n^m=n\times\left(n-1\right)\times\ldots\times(n-m+1)=\frac{n!}{\left(n-m\right)!} Anm=n×(n1)××(nm+1)=(nm)!n!

基于此,可得到求解本题的完整代码:

/*
    MT2227 饿饿!饭饭! 
    排列问题:从 k 个不同物品中选 3*w 个不同物品的方案数 
*/
#include<bits/stdc++.h> 
using namespace std; 

typedef long long ll;
// 从 n 中选取 m 个物品的排列数 
ll A(ll n, ll m){
    ll ans = 1;
    for(int i=n; m>=1; i--, m--){
        ans *= i;
    }
    return ans;
}

int main()
{
    // 获取输入
    int n,k,w; 
    cin>>n>>k>>w;
    // 格式化输出总方案数
    cout<<A(k,3*w)<<endl; 
    return 0;
} 


MT2228 甜甜花的研究

难度:黄金    时间限制:1秒    占用内存:128M
题目描述

小码哥酷爱研究植物,他对甜甜花的研究无人能及,可他仍然在不断研究着。现在小码哥有 n n n 粒甜甜花的种子,每一粒种子都能长出不同的甜甜花,由于种子实在太多,小码哥一个人实在无法照料,于是他雇佣了 m m m 位种植能手,第 i i i 个人能照料   a i   {\ a}_{i\ }  ai  株甜甜花,请问小码哥有多少种分配方式将这些种子分配出去?

格式

输入格式:第一行输入以空格隔开的两个整数 N N N K K K
     第二行输入 m m m 个正整数,分别代表   a i   {\ a}_{i\ }  ai 

输出格式:输出一个整数表示方法个数;
     由于结果可能很大,须将结果对 12520 取模。

样例 1

输入:

5 2
3 1

输出:

20

备注

其中: n ≤ 10 4 ,   m ≤ 100 ,     a i   ≤ 100 n\le{10}^4,\ m\le100,\ {\ a}_{i\ }\le100 n104, m100,  ai 100
数据保证种子有剩余。


相关知识点:排列组合


题解


对题目的简化描述如下:现有 n n n 粒不同种子, m m m 个不同盒子(各盒子的容量分别为 a 1 , a 2 , … , a m a_1, a_2, … ,a_m a1,a2,,am)。在保证种子数大于所有盒子容量之和的前提下,问有多少种不同的分装方案。

这显然是一个组合问题。例如,当 n = 8 , m = 3   ( a 1 = 1 , a 2 = 2 , a 3 = 3 ) n=8,m=3\ (a_1=1,a_2=2,a_3=3) n=8,m=3 (a1=1,a2=2,a3=3) 时,对这些种子的分装如下:

  • 盒子 1:在 8 8 8 粒种子中选择 1 1 1 粒,共 C 8 1 = 8 C_8^1=8 C81=8 种方案;
  • 盒子 2:在剩下 8 − 1 = 7 8-1=7 81=7 粒种子中选择 2 粒,共 C 7 2 = 21 C_7^2=21 C72=21 种方案;
  • 盒子 3:在剩下 7 − 2 = 5 7-2=5 72=5 粒种子中选择 3 粒,共 C 5 3 = 10 C_5^3=10 C53=10 种方案。

因此总的分装方案数有 8×21×10=1680 种。

下面给出组合数的计算公式:

C n m = n × ( n − 1 ) × … × ( n − m + 1 ) m × ( m − 1 ) × … × 1 = n ! m ! × ( n − m ) ! C_n^m=\frac{n\times(n-1)\times\ldots\times(n-m+1)}{m\times(m-1)\times\ldots\times1}=\frac{n!}{m!\times(n-m)!} Cnm=m×(m1)××1n×(n1)××(nm+1)=m!×(nm)!n!

需要注意一点,组合数是一个关于 n n n m m m 变化非常大的函数,因此实现时最好采取较大的数据范围和一定的优化方式(如一边除一边乘)。下面直接给出基于以上思路得到的完整代码:

/*
    MT2228 甜甜花的研究
    组合问题 
*/
#include<bits/stdc++.h> 
using namespace std; 

const int MOD = 12520;
const int M = 105;
int ary[M];

typedef long long ll;
// 从 n 中选取 m 个物品的组合数
ll C(ll n, ll m){
    ll ans = 1;
    for(ll i=1; i<=m; i++){
        // 边乘边除 
        ans = ans*(n-m+i)/i;
    }
    return ans;
}

// 对种子的分配过程 
int Distribute(int n, int m){
    ll ans = 1;
    for(int i=0;i<m; i++){
        ans *= C(n, ary[i]);
        n -= ary[i];
    }
    return ans%MOD;
}

int main()
{
    // 获取输入
    int n, m; cin>>n>>m;
    for(int i=0; i<m; i++)
        cin>>ary[i];
    // 输出期望 
    cout<<Distribute(n, m)<<endl;
    return 0;
} 


MT2229 赌石

难度:黄金    时间限制:1秒    占用内存:128M
题目描述

富饶的璃月街道上有一家石料店,店主小码哥是个精明的商人,为了使他的赌石生意更加红火,他根据赌徒的心理设计了一个有趣的买卖规则:他在店铺的两边放了个小桶,一个桶里有 n n n 个红球,另一个有 n n n 个蓝球。每一批 2 n 2n 2n 个璞石与这些球一一对应,对每个来买璞石的客户,小码哥都会让他们在原地闭眼旋转数圈后走向一个小桶,若拿到蓝球则可免费获得一块石头,但若拿到红球则需要付出两倍的价钱。

假设每个人每次拿到蓝球和红球的概率相同,现在请你求出一个桶里没球而另一个桶里还剩两个球的概率,精确到小数点后四位。

格式

输入格式:输入一个正整数代表这批璞石的个数(不大于2500,且保证为偶数)。
输出格式:输出一个四位小数代表所求答案。

样例 1

输入:
256

输出:

0.9500


相关知识点:排列组合


题解


对题目的简化描述如下:有两个盒子,一个盒子盛有 n n n 个蓝球,一个盒子盛有 n n n 个红球。现对这两个盒子进行随机抽取,求最终场上出现一个盒子剩 2 个球,而另一个没有球的概率。

这道题并没有说停止摸球的条件,比如当出现一个盒子没有球,而另一个盒子还剩 3、4、……、 n n n 个球时要如何算?而是直接问出现某种情况的概率。从官方给出的答案看,其对本题模型做出了以下归结:即最终只会存在 3 种情况作为结束时的状态(如下表所示)。

在这里插入图片描述

这也就是说,本题默认将 “一个盒子没有球,而另一个盒子还剩 3、4、……、 n n n 个球” 的情形最终归结为 “一个盒子剩 2 个球,另一个没有球” 这种情况。即认为:系统会一直摸球,直到摸到 2 n − 2 2n-2 2n2 个球为止(当出现某个盒子已经没有球时,下一次摸球就只会从剩余有球的盒子里摸取)。下面我们基于这一设定进行分析。

题目要求的是 “一个盒子剩 2 个球,另一个没有球” 的概率,而从上面的分析可知,这类情况实际上包含了许多状态:即一个盒子没有球,而另一个盒子有2、3、4、……时都为符合要求的状态。这时,可以通过计算这些状态的出现概率(得到通项),然后再以级数的方式进行归结。这种方法有一定的难度,并且过于繁琐。在概率论里,对此类题更常用的处理方式是通过计算对立事件的概率,进而得到原事件的概率。在本题中(从两个各含 n n n 个球的盒子中随机摸 2 n − 2 2n-2 2n2 个球),原事件 “一个盒子没有球,而另一个盒子还剩 2 个球” 的对立事件为 “两个盒子各剩下一个球”。于是接下来我们分析 “两个盒子各剩一个球的概率”。

由于摸出球的总数为 2 n − 2 2n-2 2n2 ,如果要求最终两个盒子各剩一个球,那就要求摸出的这 2 n − 2 2n-2 2n2 个球里,有一半的球来自指定盒子。由于题目说了每个人每次拿到蓝球和红球的概率相等,因此每一次摸球时,此球为蓝色(或红色)的概率是相等的,均为 1 2 \frac{1}{2} 21 。基于此可知:摸出 2 n − 2 2n-2 2n2 个球里, n − 1 n-1 n1 个球同色的概率为

p = C 2 n − 2 n − 1 ( 1 2 ) n − 1 ( 1 2 ) n − 1 = C 2 n − 2 n − 1 ( 1 2 ) 2 n − 2 p=C_{2n-2}^{n-1}\left(\frac{1}{2}\right)^{n-1}\left(\frac{1}{2}\right)^{n-1}=C_{2n-2}^{n-1}\left(\frac{1}{2}\right)^{2n-2} p=C2n2n1(21)n1(21)n1=C2n2n1(21)2n2

根据该概率值,可以得到其对立事件的概率为:

1 − p = 1 − C 2 n − 2 n − 1 ( 1 2 ) 2 n − 2 1-p=1-C_{2n-2}^{n-1}\left(\frac{1}{2}\right)^{2n-2} 1p=1C2n2n1(21)2n2

这便是本题的答案。

注意到一件事,题目给出的取值范围为 2 n ≤ 2500 2n\le2500 2n2500,即 n n n 最大可取到 1250,这样的范围在求组合数时必定存在过于庞大的数,这意味着即使用 long double 型也可能会越界。因此我们必须简化对该值的求解,即考虑拆分 C 2 n − 2 n − 1 ( 1 2 ) 2 n − 2 C_{2n-2}^{n-1}\left(\frac{1}{2}\right)^{2n-2} C2n2n1(21)2n2 ,使得计算过程能被优化为较小的数之间的四则运算(即找到一个通项式)。

C 2 n − 2 n − 1 ( 1 2 ) 2 n − 2 = ( 2 n − 2 ) ! ( n − 1 ) ! ( n − 1 ) ! × ( 1 4 ) n − 1 = ( 2 n − 2 ) × ( 2 n − 1 ) × … × 1 ( n − 1 ) ! ( n − 1 ) ! × 1 4 n − 1 = ( 2 n − 2 ) × ( 2 n − 1 ) × … × n ( n − 1 ) ! × 1 4 n − 1 = ( 2 n − 2 ) × ( 2 n − 1 ) × … × n 4 n − 1 × [ ( n − 1 ) × ( n − 2 ) × … × 1 ] = ( n − 1 + n − 1 ) × ( n − 1 + n − 2 ) × … × ( n − 1 + 1 ) 4 ( n − 1 ) × 4 ( n − 2 ) × … × 4 ( 1 ) = ∏ i = 1 n − 1 ( n − 1 + i ) 4 i \begin{align} \nonumber &C_{2n-2}^{n-1}\left(\frac{1}{2}\right)^{2n-2} \\ \nonumber &=\frac{\left(2n-2\right)!}{(n-1)!(n-1)!}\times\left(\frac{1}{4}\right)^{n-1} \\ \nonumber &=\frac{\left(2n-2\right)\times\left(2n-1\right)\times\ldots\times1}{(n-1)!(n-1)!}\times\frac{1}{4^{n-1}} \\ \nonumber &=\frac{\left(2n-2\right)\times\left(2n-1\right)\times\ldots\times n}{(n-1)!}\times\frac{1}{4^{n-1}} \\ \nonumber &=\frac{\left(2n-2\right)\times\left(2n-1\right)\times\ldots\times n}{4^{n-1}\times\left[(n-1)\times\left(n-2\right)\times\ldots\times1\right]} \\ \nonumber &=\frac{\left(n-1+n-1\right)\times\left(n-1+n-2\right)\times\ldots\times(n-1+1)}{4(n-1)\times4\left(n-2\right)\times\ldots\times4(1)} \\ \nonumber &=\prod_{i=1}^{n-1}\frac{\left(n-1+i\right)}{4i} \end{align} C2n2n1(21)2n2=(n1)!(n1)!(2n2)!×(41)n1=(n1)!(n1)!(2n2)×(2n1)××1×4n11=(n1)!(2n2)×(2n1)××n×4n11=4n1×[(n1)×(n2)××1](2n2)×(2n1)××n=4(n1)×4(n2)××4(1)(n1+n1)×(n1+n2)××(n1+1)=i=1n14i(n1+i)

如此,就将原来求组合数的过程转换为求若干子式(小范围)的乘积。

于是得到 “一个盒子没有球,而另一个盒子还剩2个球” 的概率为:

1 − ∏ i = 1 n − 1 ( n − 1 + i ) 4 i 1-\prod_{i=1}^{n-1}\frac{\left(n-1+i\right)}{4i} 1i=1n14i(n1+i)

下面给出基于以上思路得到的完整代码:

/*
    MT2229 赌石
*/
#include<bits/stdc++.h> 
using namespace std; 

// 从两个含有 n 个球的桶中取数,最终情况为 0-2 的概率 
double getProbability(int n)
{
    double ans = 1;
    for(int i=1; i<n; i++)
        ans *= (n-1+i)/(i*4.0);
    return 1-ans;
} 

int main()
{
    // 获取输入 
    int n; cin >> n;
    // 格式化输出 
    printf("%.4lf\n", getProbability(n/2));
    return 0;
} 


MT2230 square

难度:钻石    时间限制:3秒    占用内存:128M
题目描述

在一个 m × n m×n m×n 的矩阵上,小码哥在左下角的顶点出现了,他只能沿着路径向上或者向右走,他的目标是 “蠕动” 到右上角的顶点,问他有多少路径可以选择。嗯,这个、这个、这个似乎地球人都知道怎么做,但是请注意,我有个条件没给呢! m m m n n n 现在的最大范围是 50000,这可怎么办?仔细想想吧。

格式

输入格式:只有一行,包含两个整数 m m m n n n,其均不小于 4,上限均为 50000。
输出格式:由于最后的答案数目过大,所以只检查后 100 位,输出时每行十个数字,没空格间隔,共十行,如果答案位数没超过 100 位,则需要在空位上补 0。

样例 1

输入:
7 4

输出:

0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000000
0000000330


相关知识点:排列组合


题解


求从矩阵左下角到右上角的行走方案数(每次只能移动一个单位)是一个经典的迷宫问题。解决这个问题最简单的办法是动态规划,因为任意非边缘(这里的边缘主要是指与初始位置相邻的两侧,如本题中就是左侧和下侧)位置的前一个位置必定是左或下,因此可以很轻松地得到状态转移方程为:

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] dp[i][j]=dp[i-1][j]+dp[i][j-1] dp[i][j]=dp[i1][j]+dp[i][j1]

由于这种行走方式随矩阵规格变化非常剧烈,因此大多数题目都要求记录一个对指定数的模,而本题则要求记录所有行走方式总量的末尾指定长度数串。在这样的要求下,递推求解的思路变得不再适用。
对于走格子矩阵问题(假设其规格为 m × n m\times n m×n ,表示横向有 m m m 条路径,纵向有 n n n 条路径),首先要肯定一点:从左下角走到右上角(每次移动一个单位且不可倒退),其一定会走 m + n m+n m+n 步,其中 m m m 步向右 n n n 步向上。而各移动方案之间的差异无非就是 “向上” 和 “向右” 的次序不同。例如,对于一个 2 × 3 2\times3 2×3 的矩阵,其行走方案根据 “向上” 和 “向右” 次序的不同,可分为以下 10 种不同的行走方案:

→ → ↑ ↑ ↑ → ↑ → ↑ ↑ → ↑ ↑ → ↑ → ↑ ↑ ↑ → ↑ → → ↑ ↑ ↑ → ↑ → ↑ ↑ → ↑ ↑ → ↑ ↑ → → ↑ ↑ ↑ → ↑ → ↑ ↑ ↑ → → →→↑↑↑ \\ →↑→↑↑ \\ →↑↑→↑ \\ →↑↑↑→ \\ ↑→→↑↑ \\ ↑→↑→↑ \\ ↑→↑↑→ \\ ↑↑→→↑ \\ ↑↑→↑→ \\ ↑↑↑→→ \\ →→↑↑↑→↑→↑↑→↑↑→↑→↑↑↑→↑→→↑↑↑→↑→↑↑→↑↑→↑↑→→↑↑↑→↑→↑↑↑→→

因此,走格子矩阵问题也能通过求组合数得到,此时其含义即为 “在 m + n m+n m+n 步中,选 n n n 步向上走”(或 “在 m + n m+n m+n 步中,选 m m m 步向右走”)的总方案数。即 C m + n n C_{m+n}^n Cm+nn (或 C m + n m C_{m+n}^m Cm+nm)。另一方面,本题中的 m m m n n n 最大可取到 5000,这对组合数而言,是一个十分庞大的数据(任何数据类型都无法存储下),就算采取 “边乘边除” 的策略也没有一个数据类型能对中间结果进行存储,因此我们现在的问题是如何实现大范围的组合数求解?

在前面质数章节曾提到,任意一个大于 1 的正整数 n u m num num 都可以分解为若干质因数的乘幂之积:

n u m = p 1 α 1 × p 2 α 2 × … × p k α k num=p_1^{\alpha_1}\times p_2^{\alpha_2}\times\ldots\times p_k^{\alpha_k} num=p1α1×p2α2××pkαk

基于此,我们可将求组合数的过程进行转换:每对一个数进行乘法运算时,不直接算出乘积,而是记录当前乘数可被划分为哪些质因数之积,并对这些质因数进行相应记录。显然,对组合数分子部分的阶乘要累加记录各乘数的质因数,对组合数分母部分的阶乘要累减记录各乘数的质因数。例如,求组合数 C 20 5 C_{20}^5 C205 的过程如下(设质因数数组为 factor[ ]={0}):

C 20 5 = 20 × 19 × 18 × 17 × 16 5 × 4 × 3 × 2 × 1 C_{20}^5=\frac{20\times19\times18\times17\times16}{5\times4\times3\times2\times1} C205=5×4×3×2×120×19×18×17×16

阶段一:统计分子部分的阶乘。

  • 分解 20 = 2 2 × 5 1 20=2^2\times5^1 20=22×51 ,于是有:factor[2]={2},factor[5]={1};
  • 分解 19 = 19 1 19={19}^1 19=191,于是有:factor[19]={1};
  • 分解 18 = 2 1 × 3 2 18=2^1\times3^2 18=21×32,于是有:factor[2]={3},factor[3]={2};
  • 分解 17 = 17 1 17={17}^1 17=171,于是有:factor[17]={1};
  • 分解 16 = 2 4 16=2^4 16=24,于是有:factor[2]={7}。

阶段二:统计分母部分的阶乘。

  • 分解 5 = 5 1 5=5^1 5=51 ,于是有:factor[5]={0};
  • 分解 4 = 2 2 4=2^2 4=22 ,于是有:factor[2]={5};
  • 分解 3 = 3 1 3=3^1 3=31 ,于是有:factor[3]={1};
  • 分解 2 = 2 1 2=2^1 2=21 ,于是有:factor[2]={4}。

两个阶段结束后,最终得到的质因子数组内容为:factor[3]={1}、factor[5]={1}、factor[17]={1}、factor[19]={1}。将这些质因数按其对应次数进行叠乘,即得到 C 20 4 C_{20}^4 C204 的值: 2 4 × 3 1 × 17 1 × 19 1 = 15504 2^4\times3^1\times{17}^1\times{19}^1=15504 24×31×171×191=15504

采取这样的方式,可将组合数在计算过程中涉及到的阶乘运算巧妙地转换为若干个质因数的乘积过程,从而避免了组合数在大数情况下容易越界的风险。另一方面,如果直接采取大数运算的方式求组合数,会出现大数除法的情况,采取质因数分解的办法可避免出现除法。

由于前面已经给出了 质因数分解 以及 大数乘法 的相关实现,在此就直接给出基于以上思路得到的求解本题目的完整代码:

/*
    MT2230 squre
    求组合数 、质因数分解、大数乘法 
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 7;
const int M = 100; 
// 质因数数组 
int factor[N];
// 大数乘法的结果数组
int ans[M+5];
// 大数乘法(单精度*高精度) 
void multi(int x)
{
    // 将原数组中的各数与指定数相乘
    for(int i=1; i<=M; i++)
        ans[i] *= x;
    // 进位处理 
    for(int i = 1; i <= M; i++) {
        ans[i + 1] += ans[i] / 10;
        ans[i] %= 10;
    }
}
int main()
{
    int m, n, x;
    cin>>m>>n;
    // 尽可能减少计算量 
    if(m>n) swap(m, n);
    // 记录 C(m, n) 分子 n*(n-1)*…*(n-m+1) 的质因数 
    for(int i=0; i<m; i++) {
        x= m+n-i;
        for(int j = 2; j<=x/j; j++) {
            while(x%j == 0) {
                x /= j;
                factor[j]++;
            }
        }
        if(x != 1)
            factor[x]++;
    }
    // 记录 C(m, n) 分母 m! 的质因数(约去) 
    for(int i=1; i<=m; i++) {
        x = i;
        for(int j = 2; j<=x/j; j++) {
            while(x%j == 0) {
                x /= j;
                factor[j]--;
            }
        }
        if(x != 1)
            factor[x]--;
    }
    // 遍历保存的质因数,通过大数乘法统计结果 
    ans[1] = 1;
    for(int i=1; i < N; i++) {
        while(factor[i]) {
            multi(i);
            factor[i]--;
        }
    }
    // 格式化输出 
    for(int i=M; i>=1; i--) {
        cout<<ans[i];
        if(i%10 == 1) cout<<endl;
    }
    return 0;
}

END


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1099376.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

双目项目实战---测距(获取三维坐标和深度信息)

目录 1.简介 2.模块讲解 2.1 立体校正 2.1.1 校正目的 2.1.2 校正方法 2.2 立体匹配和视差计算 2.3 深度计算 3.完整代码 1.简介 双目视觉是一种通过两个摄像机&#xff08;或者两个镜头&#xff09;同时拍摄到同一个场景&#xff0c;再通过计算机算法来获取该场景深度…

C++ - 智能指针的定制删除器

前言 在上一篇C 文章当中&#xff0c;对 智能指针的使用&#xff0c;做了很详细的介绍&#xff0c;对 C11 和 C98 库当中实现的一些常用智能指针做了很详细的介绍&#xff0c;但是智能指针的使用还有一些拓展用法。上篇文章链接&#xff1a;C - 智能指针 - auto_ptr - unique_…

Stm32_标准库_16_串口蓝牙模块_手机与蓝牙模块通信_手机传入信息能对芯片时间日期进行更改

实现了手机发送信息给蓝牙模块&#xff0c;程序对数据进行分析拆解&#xff0c;并更新自身数据 main.c: #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Serial.h" #include "Ti…

无人驾驶路径规划(一)全局路径规划 - RRT算法原理及实现

前言&#xff1a;由于后续可能要做一些无人驾驶相关的项目和实验&#xff0c;所以这段时间学习一些路径规划算法并自己编写了matlab程序进行仿真。开启这个系列是对自己学习内容的一个总结&#xff0c;也希望能够和优秀的前辈们多学习经验。 一、无人驾驶路径规划 众所周知&a…

Google Authenticator 和gitlab使用的方法配置Google AuthenticatorGoogle

Google Authenticator 和gitlab使用的方法 目录概述需求&#xff1a; 设计思路实现思路分析1.配置过程&#xff1a; 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a bette…

D201126 D201129 支持以太网高级物理层(APL)

D201126 D201129 支持以太网高级物理层(APL) 全球技术和软件领导者艾默生宣布了基于其无限自动化愿景&#xff0c;并作为其下一代以软件为中心的工业自动化架构的基础。新技术的发布将超越传统的控制系统&#xff0c;创建一个更先进的自动化平台&#xff0c;为人类和塑造世界…

【网络】网络层协议:IP(待更新)

文章目录 IP 协议1. 基本概念2. IP 报头解析 &#x1f53a;IP 的 网段划分&#xff1a; IP 协议 IP 不是面向字节流的&#xff0c;而是发送接收一个个的数据包 1. 基本概念 主机&#xff1a;配有 IP 地址的设备 路由器&#xff1a;配有单个或多个 IP 地址&#xff0c;且能进行…

【1314. 矩阵区域和】

目录 一、题目描述二、算法思想三、代码实现 一、题目描述 二、算法思想 三、代码实现 class Solution { public:vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {//先预处理数组int nmat.size();//行int mmat[0].size();…

flutter开发实战-下拉刷新与上拉加载更多实现

flutter开发实战-下拉刷新与上拉加载更多实现 在开发中经常遇到列表需要下拉刷新与上拉加载更多&#xff0c;这里使用EasyRefresh&#xff0c;版本是3.3.21 一、什么是EasyRefresh EasyRefresh可以在Flutter应用程序上轻松实现下拉刷新和上拉加载。它几乎支持所有Flutter Sc…

C++指针和引用

1、引用必须初始化&#xff0c;指针不必&#xff0c;所以说引用使你更安全的指针&#xff1b; 2、在汇编代码&#xff0c;指针和引用一模一样&#xff1b; 3、引用只有一级引用&#xff0c;没有多级引用&#xff1b; 4、引用必须引用一个能取地址的变量&#xff1b; 左值&…

第三章 内存管理 五、动态分区分配算法(首次适应算法、最佳适应算法、最坏适应算法、临近适应算法)

目录 一、首次适应算法 1、算法思想&#xff1a; 2、如何实现&#xff1a; 3、两种常用的数据结构: &#xff08;1&#xff09;空闲分区表、空闲分区链 4、例子 二、最佳适应算法 1、算法思想: 2、如何实现: 3、例子&#xff1a; 三、最坏适应算法 1、算法思想&…

蓝桥杯每日一题2023.10.16

数的分解 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 最开始想使用dfs&#xff0c;发现范围过大无法在规定时间运行 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int a[N], v[N], ans; void dfs(int dep, int sum, int start) {if(sum > 20…

Linux命令行下查看实时网速

Linux命令行下&#xff0c;用ifconfig可以看到每个网卡实时的收发的数据包了字节数&#xff0c;但并不方便查看当前网卡的实时网速。 近日偶得一个软件叫nload可以方便的在命令行下查看实时网速&#xff0c;不敢独享&#xff0c;分享给大家。 1、安装 sudo apt install nload…

第三章 内存管理 六、基本分页存储管理

目录 一、定义 二、例子 三、总结 一、定义 基本分页存储管理是一种操作系统的存储管理技术。在基本分页存储管理中&#xff0c;物理内存被划分成固定大小的块&#xff0c;称为页面&#xff08;Page&#xff09;&#xff0c;而程序代码和数据被分成相同大小的块&#xff0c;…

数据结构:二叉树(1)

目录 树的概念 树的表示形式 二叉树 二叉树的性质 题目 二叉树的存储 链式存储 初始化二叉树 二叉树的遍历 前序遍历&#xff1a;根&#x1f449;左子树&#x1f449;右子树 中序遍历&#xff1a;左子树&#x1f449;根&#x1f449;右子树 后序遍历&#xff1a;左子…

二十八、【滤镜】

文章目录 滤镜库Camera Raw滤镜神经网络滤镜(Neurel Filters)液化其他滤镜 滤镜库 可以在滤镜库中选择一些常用的滤镜方式&#xff0c;主要有六大类&#xff1a; Camera Raw滤镜 Camera Raw滤镜是photoshop最重要的一个滤镜&#xff0c;他帮助我们在摄影后期去进行颜色预处…

导数、偏导数、方向导数

一、导数 导数是描述函数变化率的数学概念。 导数的定义式: 那么就有一个问题&#xff0c;为什么求函数的最大值点是要对其自变量求导&#xff0c;并使其导数等于0 比如:求L(θ)3lnθ2ln(1-θ)的最大值点 既然导数表示函数的变化率&#xff0c;那么当函数L(θ)的导数等于0&…

Go语言基础之包

包&#xff08;package&#xff09; Go语言中支持模块化的开发理念&#xff0c;在Go语言中使用包&#xff08;package&#xff09;来支持代码模块化和代码复用。一个包是由一个或多个Go源码文件&#xff08;.go结尾的文件&#xff09;组成&#xff0c;是一种高级的代码复用方案…

构建高性能物联网数据平台:EMQX和CnosDB的完整教程

CnosDB 是一款高性能、高压缩率、高易用性的开源分布式时序数据库。主要应用场景为物联网、工业互联网、车联网和IT运维。所有代码均已在GitHub开源。本文将介绍如何使用EMQX 这一MQTT 服务器 CnosDB 构建物联网数据平台&#xff0c;实现物联网数据的实时流处理。 前言 在物联…

CLIP和改进工作

CLIP和改进工作 CLIP 改进方向 语义分割 Lseg、GroupViT 目标检测 ViLD、GLIP v1/v2 视频理解 VideoCLIP、CLIP4clip、ActionCLIP 图像生成 VQGAN-CLIP、CLIPasso、CLIP-Draw 多模态下游任务 VL Downstream 其他 prompt enginering&#xff08;CoOp等&#xff09; depthCLIP、…