NEYC 1702 排座 问题模型

30 篇文章 0 订阅
30 篇文章 1 订阅

零、概述

大家好,我是 NEYC 1702 GGN。高二下学期,1702班 所采用的随机排座程序是我编写的,虽然我在这方面投入了不少精力,但是我的程序还是暴露出了很多问题。经过一段时间的反思,我决定再一次修改我的排座程序。这一次的拍做程序中,我将采用理论上完全符合古典概型的随机生成程序。另一方面,我也会对 《2019.4.26 排座协议》 进行一系列的修改调整。

不同于前几次对于排座程序的 “实践性” 、“感性”的修改,这次我将以 数学理论 为基础证明我的排座程序的随机性。由于我的数学水平十分有限,非常欢迎老师们、同学们、家长们指出下文中不合理的地方,我会尽快对这些错误进行修正。

一、数学模型的构建

什么是 “一个合法的排座方案”?

班级中每次参与排座的人数是不确定的,这个我们的排座模型的建立带来了很大的困难。因此,在排座模型中,我们规定,1702班每次参与排座的同学数量为 30 人,其中女生 5 人,男生25人。为了方便起见,下文中,我们用编号1 ~ 5来表示女生,编号6 ~ 30表示男生。显然,编号的分配并不影响事件发生的概率(在此不作赘述)。根据惯例,我们规定,女生必须被安排在班级的前三行。

“一个合法的排座方案” 是 一个满足下列条件的矩阵

  1. 行数为5,列数为6
  2. 矩阵中的每一个元素 a 都满足 a ∈ { 1 , 2 , 3 , . . . , 30 } a \in \{1, 2, 3, ..., 30\} a{1,2,3,...,30}
  3. 矩阵中不存在两个位置不同但数值相同的元素
  4. 如果属于集合 { 1 , 2 , 3 , 4 , 5 } \{1, 2, 3, 4, 5\} {1,2,3,4,5}的任意一个元素 存在于矩阵中,那么该元素一定存在于矩阵的前三行。

这四条定义是十分容易理解的,在此不做赘述。下文中我们将用集合 U U U来表示所有合法排座方案组成的集合。

何谓 “生成程序符合古典概型”

符合古典概型就是指:程序生成出任何两种不同的座位图的 概率 是相同的。

即: ∀ E , F ∈ U , E ≠ F ⇒ P ( E ) = P ( F ) \forall E,F \in U,E\neq F \Rightarrow P(E)=P(F) E,FU,E̸=FP(E)=P(F)

二、矩阵的生成

总合法方案数(下文中用符号 C a r d ( U ) Card(U) Card(U) 表示)

C a r d ( U ) = A 18 5 A 25 25 ≈ 2.658001 × 1 0 28 Card(U)=A_{18}^5 A_{25}^{25} \approx 2.658001 \times 10^{28} Card(U)=A185A25252.658001×1028

其中 A m n A_m^n Amn表示 从m个不同元素中抽取n个不同元素的排列数。

解释:从前三排的18个座位中抽取5个座位依次分配给每个女生,然后所有的男生的随机占据当前未被占据的25个位置。

随机排列算法

对于一个长度为n的数列arr我们给出了这样一种随机排列的方式:我们利用系统自带的随机数生成器为数列中的每一个元素 生成一个属于 [ 0 , 2 30 ) [0, 2^{30}) [0,230)范围内的随机整数(即程序中的函数RND)作为它的权值,然后把数列按照权值进行排序,得到的新数列即为随机打乱后的数列。可以证明,如果计算机生成的随机权值是完全随机的,那么得到的数列就是随机打乱的。(其实应该证明一下的,不过我比较懒。。)

namespace Permutation { /// 用于生成随机排列 
    struct Pair {
        int val, rght; /// val 实际值, rght 权值 
    } ns[30 + 3];
    
    bool Cmp(const Pair& p1, const Pair& p2) { /// 用于随机排列 
        return p1.rght < p2.rght; /// 按照权值升序排列 
    }
    
    void MakeRndPermutation(int* arr, int n) { /// 构造随机排列 
        memset(ns, 0x00, sizeof(ns));
        for(int i = 1; i <= n; i ++) {
            ns[i] = (Pair){arr[i], RND()}; /// 载入数据 
        }
        sort(ns+1, ns+n+1, Cmp); /// 随机排列 
        for(int i = 1; i <= n; i ++) {
            arr[i] = ns[i].val; /// 存回数据 
        }
    }
}

随机排座算法

我们可以利用上文中提及的随机排列算法去实现随机排座的过程:

最开始的时候,我们定义了一个5行6列的矩阵,它的每一个位置上的元素都是0。

先构造一个长度为18的数列,这个数列中的前五个元素为1~ 5,其余元素都为零。然后将这个数列打乱,将打乱后的数列中,下标为1 ~ 6的六个元素 从左到右依次填充到矩阵的第一行的位置上,同理,下标为7 ~ 12 的六个元素 从左到右一次填充到矩阵的第二行的位置上,下标为 13 ~ 18 的六个元素 从左到右依次填充到矩阵的第三行的位置上。这样就完成了对女生座位的安排。然后我们再构造另一个长度为25的数列,其下标为k的位置上的数值为k+5。然后再对这个数列也进行随机打乱,并将这个数列中的元素 按照从矩阵第一行到矩阵第五行的顺序依次填充到矩阵中每一个当前数值为0的位置上。

namespace Graph { /// 生成座位图 
    /// 学号 1~30, 其中 1~5 为女生, 6~30 为男生
    /// 女生必须坐在前三排 
    int store[7][7]; /// 6 * 6 的座位图 
    int posx[30 + 3], posy[30 + 3]; /// 每个同学的位置 
    
    void Construct() { /// 构造一个座位图 
        int arr[30 + 3] = {}, tmp = 1;
        memset(store, 0x00, sizeof(store)); /// 注意数据清零 
        /// posx posy 理论上会被完全覆盖, 再次不再清零 
        
        /// 安排女生 
        for(int i = 1; i <= 5; i ++) {
            arr[i] = i;
        }
        Permutation::MakeRndPermutation(arr, 18); /// 随机排列 
        for(int i = 1; i <= 18; i ++) {
            store[(i-1)/6 + 1][(i-1)%6 + 1] = arr[i];
            //printf("set %d at %d, %d\n", arr[i], (i-1)/6 + 1, (i-1)%6 + 1);
        }
        
        /// 安排男生 (tmp 表示接下来要安置 学号为arr[tmp]的男生) 
        for(int i = 6; i <= 30; i ++) {
            arr[i - 5] = i; 
        }
        Permutation::MakeRndPermutation(arr, 25);
        for(int i = 1; i <= 5; i ++) {
            for(int j = 1; j <= 6; j ++) {
                if(store[i][j] == 0) { /// 发现空位 
                    store[i][j] = arr[tmp ++]; /// 男生填空 
                }
            }
        }
        
        /// 生成好的排列储存在 store 中 
        for(int i = 1; i <= 5; i ++) {
            for(int j = 1; j <= 6; j ++) {
                posx[store[i][j]] = i;
                posy[store[i][j]] = j; /// 记录坐标信息 
            }
        }
    }
    
    void OutputToScreen() { /// 显示座位图 
        for(int i = 1; i <= 5; i ++) {
            for(int j = 1; j <= 6; j ++) {
                printf("%3d", store[i][j]);
            }
            puts("");
        }
        puts("");
    }
    
    bool CheckAdjoin(int i, int j) { /// 检测两人是否相邻 
        return abs(posx[i]-posx[j]) + abs(posy[i]-posy[j]) == 1;
    }
}

三、同学们最喜欢的概率计算问题

0.何谓 “相邻”

我们用 p o s x ( k ) = x posx(k)=x posx(k)=x表示编号为k的同学在座位图中位于第x行。

我们用 p o s y ( k ) = y posy(k)=y posy(k)=y表示编号为k的同学在作为图中位于第y列。

我们定义两个同学p、q相邻 ⇔ ∣ p o s x ( p ) − p o s x ( q ) ∣ + ∣ p o s y ( p ) − p o s y ( q ) ∣ = 1 \Leftrightarrow|posx(p)-posx(q)|+|posy(p)-posy(q)|=1 posx(p)posx(q)+posy(p)posy(q)=1,即其中一个同学位于另一个同学的正前、正后、正左、正右四个方向上的相邻位置。

1.指定两个男生a和b,求他们相邻的概率

根据古典概型可知,ab两人相邻的概率为 ab两人相邻条件下的合法方案总数 除以总方案数 C a r d ( U ) Card(U) Card(U)

首先,ab两人的相对位置关系有如下相中可能出现的情况——左右相邻 or 前后相邻:

在这里插入图片描述

但是,实际情况中我们还需要考虑着两个同学的位置是否位于前三排。

假如这个图是班级的座位图:

在这里插入图片描述

那么,这两个人的座位可能有如下的几种情况:

在这里插入图片描述

上述五个图片分别代表了ab两人中“有两人位于前三排”、“有一人位于前三排”、“没有人位于前三排”这三种情况。

至此我们可以计算出ab两人相邻的概率(下文中我们用符号 p ′ p&#x27; p表示指定的两名男生处于相邻位置的概率):

p ′ = A 2 2 × ( 27 × A 16 5 + 6 × A 17 5 + 16 × A 18 5 ) × A 23 23 C a r d ( U ) ≈ 0.1136601307 ≈ 11.36 % p&#x27;=\frac{A_2^2\times(27\times A_{16}^5+6\times A_{17}^5+16\times A_{18}^5)\times A_{23}^{23}}{Card(U)} \approx 0.1136601307 \approx 11.36\% p=Card(U)A22×(27×A165+6×A175+16×A185)×A23230.113660130711.36%

理论计算的值与实际实验测得的概率惊人的相近,我进行了五次随机统计实验,每次统计实验中包含一千万次随机试验(注意区分实验试验——said c s c csc csc),这五次试验测得的概率分别为: 0.1135343 , 0.1136461 , 0.1135605 , 0.1135023 , 0.1137523 0.1135343, 0.1136461, 0.1135605, 0.1135023, 0.1137523 0.1135343,0.1136461,0.1135605,0.1135023,0.1137523,与理论值的相对误差不超过 1 % 1\% 1%

2.指定三个男生 a,b,c ,要求a与b相邻,且a与c相邻,求概率

这个概率的计算过程相对复杂,我仍采用了上一个问题中的,枚举相对位置关系的方法,下图是我在学校徒手计算时的手稿:

在这里插入图片描述

由此可知题中所求概率(下文中我们用符号 p ′ ′ p&#x27;&#x27; p表示某一个指定男生与另两个指定的男生同时相邻的概率)为:

p ′ ′ = A 2 2 × ( 58 × A 15 5 + 16 × A 16 5 + 16 × A 17 5 + 28 × A 18 5 ) × A 22 22 C a r d ( U ) ≈ 0.0098609927 ≈ 1 % p&#x27;&#x27;=\frac{A_2^2\times(58\times A_{15}^5+16\times A_{16}^5+16\times A_{17}^5 + 28\times A_{18}^5)\times A_{22}^{22}}{Card(U)} \approx 0.0098609927 \approx 1\% p=Card(U)A22×(58×A155+16×A165+16×A175+28×A185)×A22220.00986099271%

理论计算与实验结果又是惊人的相似,我又进行了五次实验,每次试验中包含一千万次随机试验,它们统计得到的概率分别为: 0.0098445 , 0.0098264 , 0.0098552 , 0.0098357 , 0.0098327 0.0098445, 0.0098264, 0.0098552, 0.0098357, 0.0098327 0.0098445,0.0098264,0.0098552,0.0098357,0.0098327,与理论值的相对误差仍不超过 1 % 1\% 1%

3.指定四个男生 a,b,c,d ,要求a与b相邻,且a与c相邻,且a与d相邻,求概率

有了前几个问题的经验,这一个问题解决起来就十分容易了。不再赘述,相关统计结果见手稿:

在这里插入图片描述

废话我都懒得写了,直接上公式:

p ′ ′ ′ = A 3 3 × ( 26 × A 14 5 + 14 × A 15 5 + 14 × A 17 5 + 8 × A 18 5 ) × A 21 21 C a r d ( U ) ≈ 0.0005749632 ≈ 0.057 % p&#x27;&#x27;&#x27; = \frac{A_3^3\times(26\times A_{14}^5 +14\times A_{15}^5+14\times A_{17}^5+8\times A_{18}^5)\times A_{21}^{21}}{Card(U)} \approx 0.0005749632 \approx 0.057 \% p=Card(U)A33×(26×A145+14×A155+14×A175+8×A185)×A21210.00057496320.057%

列举五次实验数据(每次试验包括一千万次试验): 0.0005658 , 0.0005767 , 0.0005669 , 0.0005818 , 0.0005692 0.0005658, 0.0005767, 0.0005669, 0.0005818, 0.0005692 0.0005658,0.0005767,0.0005669,0.0005818,0.0005692。虽然说相对误差达到了 2 % 2\% 2%,但是由于这个事件发生的概率是在是太小了,所以从绝对误差角度看,这个准确程度是完全可以接受的。(相对误差 2 % 2\% 2%跟没有误差有冇区别…QwQ)

4.指定五个男生 a,b,c,d,e ,要求a与b相邻,且a与c相邻,且a与d相邻,且a与e相邻,求概率

显然,bcde这四个男生一定把男生a围在了中间,详见手稿。

在这里插入图片描述

可知:

p ′ ′ ′ ′ = A 4 4 × ( 4 × A 13 5 + 4 × A 14 5 + 4 × A 17 5 ) × A 20 20 C a r d ( U ) ≈ 0.0000166549 ≈ 0.0017 % p&#x27;&#x27;&#x27;&#x27; = \frac{A_4^4\times(4\times A_{13}^5+4\times A_{14}^5+4\times A_{17}^5)\times A_{20}^{20}}{Card(U)} \approx 0.0000166549 \approx 0.0017\% p=Card(U)A44×(4×A135+4×A145+4×A175)×A20200.00001665490.0017%

虽然我觉得这个时间发生的概率大概小到可以忽略不计了,这就意味着实验的统计值与理论值可能有较大的误差,但是我还是毅然决然地决定进行一千万次随机试验。。

实验过程中场面相当震撼:

在这里插入图片描述

一百八十万次试验竟然只检测到了34组满足要求的结果…

最后,实验统计结果为 0.0000181 0.0000181 0.0000181,虽然与理论值的误差达到了 5 % 5\% 5%以上,但考虑到绝对误差,理论预言几乎是完全准确的。

4 1 3 \frac{1}{3} 31.指定六个男生 a,b,c,d,e,f,要求a与b,c,d,e,f 同时相邻,求概率

因为,与a同时相邻的最多只有4个,所以指定的五个同学不可能同时与a相邻,即概率为0。

4 2 3 \frac{2}{3} 32.指定 k + 1 ( k ≥ 5 ) k+1 (k\geq 5) k+1(k5) 个男生 a , b 1 , b 2 , b 3 , . . . , b k a,b_1,b_2,b_3,...,b_k a,b1,b2,b3,...,bk 要求a与 b 1 , b 2 , b 3 , . . . , b k b_1,b_2,b_3,...,b_k b1,b2,b3,...,bk同时相邻,求概率

因为,与a同时相邻的最多只有4个,所以指定的 k ( k ≥ 5 ) k(k\geq 5) k(k5)个同学不可能同时与a相邻,即概率为0。

5.指定两个男生a和b,求他们不相邻的概率

我们接下来用符号 P 0 ( 1 ) P_0(1) P0(1)来表示这个值,由于a和b要么相邻,要么不相邻,所以:

P 0 ( 1 ) = 1 − p ′ ≈ 0.8863398693 ≈ 88.6 % P_0(1)=1-p&#x27;\approx 0.8863398693 \approx 88.6\% P0(1)=1p0.886339869388.6%

6.指定三个男生a,b,c,已知a与b不相邻,且a与c也不相邻,求概率

下文中我们用符号 P 0 ( 2 ) P_0(2) P0(2)表示这个值,简单的容斥原理就可以一步搞定。

记事件M表示a与b相邻,事件N表示a与c相邻,如下图韦恩图所示:

在这里插入图片描述

P 0 ( 2 ) = P ( ∁ U ( M ∪ N ) ) = 1 − P ( M ) − P ( N ) + P ( M ∩ N ) = 1 − 2 p ′ + p ′ ′ ≈ 0.7825407313 ≈ 78.3 % P_0(2)=P(\complement_U (M\cup N))=1-P(M)-P(N)+P(M\cap N)=1-2p&#x27;+p&#x27;&#x27;\approx 0.7825407313 \approx 78.3\% P0(2)=P(U(MN))=1P(M)P(N)+P(MN)=12p+p0.782540731378.3%

7.指定 k + 1 ( k ≥ 4 ) k+1(k\geq 4) k+1(k4)个男生 a , b 1 , b 2 , b 3 , . . . , b k a,b_1,b_2,b_3,...,b_k a,b1,b2,b3,...,bk 要求a与 b 1 , b 2 , b 3 , . . . , b k b_1,b_2,b_3,...,b_k b1,b2,b3,...,bk中任何一人都不相邻,

下文中我们用符号 P 0 ( k ) P_0(k) P0(k)表示这个值,仍然可以根据容斥原理表示出运算结果。

P 0 ( k ) = C k 0 − C k 1 p ′ + C k 2 p ′ ′ − C k 3 p ′ ′ ′ + C k 4 p ′ ′ ′ ′ − . . . P_0(k)=C_k^0-C_k^1p&#x27;+C_k^2p&#x27;&#x27;-C_k^3p&#x27;&#x27;&#x27;+C_k^4p&#x27;&#x27;&#x27;&#x27;-... P0(k)=Ck0Ck1p+Ck2pCk3p+Ck4p...

但是,根据问题 4 2 3 4\frac{2}{3} 432中的结论,上式中“…”所代表的的项实际上都是零,所以:

P 0 ( k ) = C k 0 − C k 1 p ′ + C k 2 p ′ ′ − C k 3 p ′ ′ ′ + C k 4 p ′ ′ ′ ′ P_0(k)=C_k^0-C_k^1p&#x27;+C_k^2p&#x27;&#x27;-C_k^3p&#x27;&#x27;&#x27;+C_k^4p&#x27;&#x27;&#x27;&#x27; P0(k)=Ck0Ck1p+Ck2pCk3p+Ck4p

8.假说演绎法:通过两种方法计算 P 0 ( 24 ) P_0(24) P0(24)的值,从而验证问题7中结论的正确性。

方法1:带入问题7结论:

P 0 ( 24 ) = C 24 0 − C 24 1 p ′ + C 24 2 p ′ ′ − C 24 3 p ′ ′ ′ + C 24 4 p ′ ′ ′ ′ ≈ 0.0070401494 P_0(24)=C_{24}^0-C_{24}^1p&#x27;+C_{24}^2p&#x27;&#x27;-C_{24}^3p&#x27;&#x27;&#x27;+C_{24}^4p&#x27;&#x27;&#x27;&#x27; \approx 0.0070401494 P0(24)=C240C241p+C242pC243p+C244p0.0070401494

方法2:由于班级中一共有25名男生,所以说 P 0 ( 24 ) P_0(24) P0(24)意味着男生a,不能与任何男生相邻,而只能与女生相邻。用分类加法计数原理统计男生a被女生团团围住的方案即可,给出手稿:

在这里插入图片描述

得到公式为:

P 0 ( 24 ) = ( 2 × A 5 2 × A 15 3 + 4 × A 5 3 × A 14 2 + 4 × A 5 4 × A 13 1 ) × A 24 24 C a r d ( U ) ≈ 0.0061904762 P_0(24)=\frac{(2\times A_5^2\times A_{15}^3+4\times A_5^3\times A_{14}^2+4\times A_5^4\times A_{13}^1)\times A_{24}^{24}}{Card(U)} \approx 0.0061904762 P0(24)=Card(U)(2×A52×A153+4×A53×A142+4×A54×A131)×A24240.0061904762

然后我们惊奇地发现两个结果竟然不一样?!!谁出错了!!?(这时我的内心是崩溃的,可能又要靠实验来说话了!)

一千万次随机试验得到的概率为 0.0070354 0.0070354 0.0070354,我的天,问题7得到的结论竟然是对的!竟然比我们后来又提出来的这个算法还要精确得多!Why?

哦!我知道了,后来提出的这种算法少考虑了两种情况!!即a同学在第二行最左侧位置和最右侧位置的情况!!加上之后再看看??

P 0 ( 24 ) = ( 2 × A 5 2 × A 15 3 + 4 × A 5 3 × A 14 2 + 4 × A 5 4 × A 13 1 + 2 × A 5 3 × A 14 2 ) × A 24 24 C a r d ( U ) ≈ 0.0070401494 P_0(24)=\frac{(2\times A_5^2\times A_{15}^3+4\times A_5^3\times A_{14}^2+4\times A_5^4\times A_{13}^1+2\times A_5^3\times A_{14}^2)\times A_{24}^{24}}{Card(U)} \approx 0.0070401494 P0(24)=Card(U)(2×A52×A153+4×A53×A142+4×A54×A131+2×A53×A142)×A24240.0070401494

我的天,竟然与理论值完全相同(精确到小数点后10位的条件下)!!

不妨把小数点移动至小数点后更多的位数:

0.12345678901234567890123456789012345678 (帮助你数位数用的一行数字)
0.00704014939309056939401552932533689955 (方法1)
0.00704014939309056956172805288168836668 (方法2)

说实话我是用C++中的long double计算的,我觉得这个精度足够了。计算得到的这两个数在小数点后的前18位都是相同的,说明它们应该是有着相同的组合意义。

四、同学们徒手就可以计算出的一些简单概率问题

1.男生a坐在前三排某一指定位置的概率

P 1 = A 17 5 × A 24 24 C a r d ( U ) = 26 9 ≈ 0.0288888889 ≈ 2.89 % P_1 = \frac{A_{17}^{5}\times A_{24}^{24}}{Card(U)} =\frac{26}{9} \approx 0.0288888889 \approx 2.89\% P1=Card(U)A175×A2424=9260.02888888892.89%

试验数据为: 0.0289321 ≈ 2.89 % 0.0289321\approx 2.89\% 0.02893212.89%,误差在合理范围内。

男生a坐在两个不同位置上从而形成的两个事件为互斥事件。由此可知,男生坐在前三排的概率为(请注意这里不是约等于):

18 P = 52 % 18P = 52\% 18P=52%

我们不妨用实验去验证一下这个计算结果: 0.5202649 ≈ 52 % 0.5202649\approx 52\% 0.520264952%,在合理误差范围内。

2.女生a坐在前三排某一指定位置的概率

P 2 = A 17 4 × A 25 25 C a r d ( U ) = 1 18 ≈ 5.56 % P_2 = \frac{A_{17}^4\times A_{25}^{25}}{Card(U)}=\frac{1}{18}\approx 5.56\% P2=Card(U)A174×A2525=1815.56%

3.男生a与女生b相邻的概率

与两男相邻有很大的相似之处,只是由于女生只能坐在前三排,所以就去掉了两人位置都在后两排的情况。

P 3 = 27 × A 2 2 × A 16 4 + 6 × A 17 4 C a r d ( U ) ≈ 0.1050980392 ≈ 10.5 % P_3 = \frac{27\times A_2^2\times A_{16}^4+6\times A_{17}^4}{Card(U)} \approx0.1050980392\approx10.5\% P3=Card(U)27×A22×A164+6×A1740.105098039210.5%

五、受影响下的实践概率

0.0280540   0.0285191   0.0284975   0.0284215   0.0285086   0.0281117
0.0284118   0.0291249   0.0289828   0.0290540   0.0290901   0.0284643
0.0286462   0.0292079   0.0292290   0.0293314   0.0293300   0.0286686
0.0396448   0.0413433   0.0412379   0.0411593   0.0413348   0.0397338
0.0390136   0.0400485   0.0399276   0.0399483   0.0399662   0.0389885

附录、“验证程序”的完整实现

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <cmath>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;

//#define double long double

/// 2019.5.31 主题: NEYC 1702 排座 问题模型 
/// 命名规则: 变量名、字段名等首字母小写, 函数名、类名等首字母大写 

int RND() { /// 利用c语言自带的随机数生成器 
    return rand()*32768 + rand();
}

namespace Permutation { /// 用于生成随机排列 
    struct Pair {
        int val, rght; /// val 实际值, rght 权值 
    } ns[30 + 3];
    
    bool Cmp(const Pair& p1, const Pair& p2) { /// 用于随机排列 
        return p1.rght < p2.rght; /// 按照权值升序排列 
    }
    
    void MakeRndPermutation(int* arr, int n) { /// 构造随机排列 
        memset(ns, 0x00, sizeof(ns));
        for(int i = 1; i <= n; i ++) {
            ns[i] = (Pair){arr[i], RND()}; /// 载入数据 
        }
        sort(ns+1, ns+n+1, Cmp); /// 随机排列 
        for(int i = 1; i <= n; i ++) {
            arr[i] = ns[i].val; /// 存回数据 
        }
    }
}

namespace Graph { /// 生成座位图 
    /// 学号 1~30, 其中 1~5 为女生, 6~30 为男生
    /// 女生必须坐在前三排 
    int store[7][7]; /// 6 * 6 的座位图 
    int posx[30 + 3], posy[30 + 3]; /// 每个同学的位置 
    
    void Construct() { /// 构造一个座位图 
        int arr[30 + 3] = {}, tmp = 1;
        memset(store, 0x00, sizeof(store)); /// 注意数据清零 
        /// posx posy 理论上会被完全覆盖, 再次不再清零 
        
        /// 安排女生 
        for(int i = 1; i <= 5; i ++) {
            arr[i] = i;
        }
        Permutation::MakeRndPermutation(arr, 18); /// 随机排列 
        for(int i = 1; i <= 18; i ++) {
            store[(i-1)/6 + 1][(i-1)%6 + 1] = arr[i];
            //printf("set %d at %d, %d\n", arr[i], (i-1)/6 + 1, (i-1)%6 + 1);
        }
        
        /// 安排男生 (tmp 表示接下来要安置 学号为arr[tmp]的男生) 
        for(int i = 6; i <= 30; i ++) {
            arr[i - 5] = i; 
        }
        Permutation::MakeRndPermutation(arr, 25);
        for(int i = 1; i <= 5; i ++) {
            for(int j = 1; j <= 6; j ++) {
                if(store[i][j] == 0) { /// 发现空位 
                    store[i][j] = arr[tmp ++]; /// 男生填空 
                }
            }
        }
        
        /// 生成好的排列储存在 store 中 
        for(int i = 1; i <= 5; i ++) {
            for(int j = 1; j <= 6; j ++) {
                posx[store[i][j]] = i;
                posy[store[i][j]] = j; /// 记录坐标信息 
            }
        }
    }
    
    void OutputToScreen() { /// 显示座位图 
        for(int i = 1; i <= 5; i ++) {
            for(int j = 1; j <= 6; j ++) {
                printf("%3d", store[i][j]);
            }
            puts("");
        }
        puts("");
    }
    
    bool CheckAdjoin(int i, int j) { /// 检测两人是否相邻 
        return abs(posx[i]-posx[j]) + abs(posy[i]-posy[j]) == 1;
    }
    bool CheckNotDivide(int i) { /// 检测同学 i周围是否有男生 
        int x = posx[i], y = posy[i];
        return store[x-1][y]>5 || store[x][y-1]>5 || store[x+1][y]>5 || store[x][y+1]>5;
        /// 由于边界之外的位置都是0 所以不需要特殊判断 
    }
}

namespace Tests { /// 一些随机试验 
    void MaleFemaleAdjoinRate() { /// 探究一男一女相邻的概率 
        int cnt = 0; /// 统计 10号和 1号相邻次数 
        int t; /// 实验次数 
        printf("请输入实验次数: "); scanf("%d", &t); 
        for(int i = 1; i <= t; i ++) {
            Graph::Construct();
            if(Graph::CheckAdjoin(10, 1)) {
                cnt ++;
            }
            if(i % 100000 == 0) {
                printf("已完成 %10d / %10d 次试验, 检测到相邻次数为: %10d \n", i, t, cnt);
            }
        }
        printf("实验结束, 指定两男相邻的概率为: %.10lf\n", (double)cnt/t);
    }
    void TwoMaleAdjoinRate() { /// 探究两男相邻的概率 
        int cnt = 0; /// 统计 10号和 13号相邻次数 
        int t; /// 实验次数 
        printf("请输入实验次数: "); scanf("%d", &t); 
        for(int i = 1; i <= t; i ++) {
            Graph::Construct();
            if(Graph::CheckAdjoin(10, 13)) {
                cnt ++;
            }
            if(i % 100000 == 0) {
                printf("已完成 %10d / %10d 次试验, 检测到相邻次数为: %10d \n", i, t, cnt);
            }
        }
        printf("实验结束, 指定两男相邻的概率为: %.10lf\n", (double)cnt/t);
    }
    void ThreeMaleAdjoinRate() { /// 探究三男相邻的概率 
        int cnt = 0; /// 统计 10号和 13号相邻 且 10号与 23号相邻 次数 
        int t; /// 实验次数 
        printf("请输入实验次数: "); scanf("%d", &t); 
        for(int i = 1; i <= t; i ++) {
            Graph::Construct();
            if(Graph::CheckAdjoin(10, 13) && Graph::CheckAdjoin(10, 23)) {
                cnt ++;
            }
            if(i % 100000 == 0) {
                printf("已完成 %10d / %10d 次试验, 检测到相邻次数为: %10d \n", i, t, cnt);
            }
        }
        printf("实验结束, 指定两男相邻的概率为: %.10lf\n", (double)cnt/t);
    }
    void FourMaleAdjoinRate() { /// 探究四男相邻的概率 
        int cnt = 0; /// 统计 10号和 13号相邻 且 10号与 23号相邻 且 10号与 25号相邻 次数 
        int t; /// 实验次数 
        printf("请输入实验次数: "); scanf("%d", &t); 
        for(int i = 1; i <= t; i ++) {
            Graph::Construct();
            if(Graph::CheckAdjoin(10, 13) && Graph::CheckAdjoin(10, 23) && Graph::CheckAdjoin(10, 25)) {
                cnt ++;
            }
            if(i % 100000 == 0) {
                printf("已完成 %10d / %10d 次试验, 检测到相邻次数为: %10d \n", i, t, cnt);
            }
        }
        printf("实验结束, 指定两男相邻的概率为: %.10lf\n", (double)cnt/t);
    }
    void FiveMaleAdjoinRate() { /// 探究五男相邻的概率 
        int cnt = 0;
        int t; /// 实验次数 
        printf("请输入实验次数: "); scanf("%d", &t); 
        for(int i = 1; i <= t; i ++) {
            Graph::Construct();
            if(Graph::CheckAdjoin(10, 13) && Graph::CheckAdjoin(10, 23) && Graph::CheckAdjoin(10, 25) && Graph::CheckAdjoin(10, 9)) {
                cnt ++;
            }
            if(i % 100000 == 0) {
                printf("已完成 %10d / %10d 次试验, 检测到相邻次数为: %10d \n", i, t, cnt);
            }
        }
        printf("实验结束, 指定两男相邻的概率为: %.10lf\n", (double)cnt/t);
    }
    void MaleDivideRate() { /// 探究P_0(24)的概率 
        int cnt = 0;
        int t; /// 实验次数 
        printf("请输入实验次数: "); scanf("%d", &t); 
        for(int i = 1; i <= t; i ++) {
            Graph::Construct();
            if(!Graph::CheckNotDivide(10)) {
                cnt ++;
            }
            if(i % 100000 == 0) {
                printf("已完成 %10d / %10d 次试验, 检测到相邻次数为: %10d \n", i, t, cnt);
            }
        }
        printf("实验结束, 指定两男相邻的概率为: %.10lf\n", (double)cnt/t);
    }
}

namespace Calculation { /// 排列数计算 
    double fac[30 + 3]; /// 计算阶乘 
    void Init() { /// 初始化数学功能 
        fac[0] = 1;
        for(int i = 1; i <= 30; i ++) {
            fac[i] = fac[i-1] * i;
        }
    }
    inline double Fact(int x) { /// 计算阶乘 
        return fac[x];
    }
    inline double A(int n, int m) { /// 计算组合数 
        return Fact(n)/Fact(n-m);
    }
    inline double C(int n, int m) {
        return Fact(n)/(Fact(n-m)*Fact(m));
    }
}
//#define Cal Calculation
using namespace Calculation;

int main() {
    Calculation::Init(); /// 初始化数学功能 
    srand(time(NULL));   /// 重置随机种子 
    double p1 = A(2, 2)*(27*A(16, 5)+6*A(17, 5)+16*A(18, 5))*A(23, 23)/(A(18, 5)*A(25, 25));
    double p2 = A(2, 2)*(58*A(15, 5)+16*A(16, 5)+16*A(17, 5)+28*A(18, 5))*A(22, 22)/(A(18, 5)*A(25, 25));
    //Tests::ThreeMaleAdjoinRate();
    double p3 = A(3, 3)*(26*A(14, 5)+14*A(15, 5)+14*A(17, 5)+8*A(18, 5))*A(21, 21)/(A(18, 5)*A(25, 25));
    //Tests::FiveMaleAdjoinRate();
    double p4 = A(4, 4)*(4*A(13, 5)+4*A(14, 5)+4*A(17, 5))*A(20, 20)/(A(18, 5)*A(25, 25));
    double p0_24_1 = 1-C(24, 1)*p1+C(24, 2)*p2-C(24, 3)*p3+C(24, 4)*p4;
    double p0_24_2 = (2*A(5, 2)*A(15, 3)+6*A(5, 3)*A(14, 2)+4*A(5, 4)*A(13, 1))*A(24, 24)/(A(18, 5)*A(25, 25));
    //printf("%.38lf\n%.38lf\n", p0_24_1, p0_24_2);
    //Tests::MaleDivideRate();
    system("pause");
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值