前言
在2017年9月的某一节晚自习上课前,NEYC 1702班不知为什么开始了一个自发的“班级大讨论”,班级中的很多同学都在“埋头苦算”,也有一些同学在争论着一些什么。这次讨论的核心话题便是英语开始中的一种题型“七选五”问题。这次有着班级大半部分同学参与进来的讨论,在很多方面都有着或多或少的指导意义。
序幕
A君:“…英语七选五我总是选不准,我真是太垃圾了。”
B君:“实在不行那就得蒙啊,说不定蒙的比选的得分还多呢!”
……
A君:“真的吗?那你怎么能证明蒙的比选的得分多呢?”
B君:“那咱们就试试呗,我心里想一组答案,你蒙一组答案,看看能得多少分。”
……
A君:“EFGAB!”
B君:“可惜料了,我想的是ABCDE @%$#^..”
……
他们来回猜了好多次,但只有少数的情况“得了分”,大多数时候都是零分。
A君:“原来瞎猜的得分概率那么低啊..诶?那你觉得如果我们瞎猜,不“暴零”的概率又是多少呢?”
B君陷入了沉思。
……
问题1:
随机生成一个回答序列,A[1..5]满足 A[i]属于{A,B,C,D,E,F,G}(1<=i<=5)并且有A[i]!=A[j] (i!=j 且 1<=i,j<=5 )(这是这个问题的难点,因为七选五就算是瞎蒙,也不会有人去蒙重复的选项!)。这个问题的答案序列B[1..5]是一个唯一确定的序列,而且也满足这两条性质。当且仅当存在A[i]=B[i] (1<=i<=5),我们说这道题没有“暴零”。求这道题不“暴零”的概率是多少。
GGN给出的解决方案:
组合数 + 容斥原理。
不暴零的方案数 = 至少对一道题的方案数 - 至少对两道题的方案数 + 至少对三道题的方案数 -至少对四道题的方案数 + 五道题都对的方案数
记至少对i道题的方案数为f(i),即:
E = f(1) - f(2) + f(3) - f(4) +f(5)
再看f(i):
至少i道题正确的方案数相当于是在从5个位置选出i个作为“正确的位”的情况下剩下5-i个位置从剩下7-i个字母里任意选的方案数,即:
f(i) = C(5,i) * P(7-i,5-i)
这样就可求出E。
总方案数很显然等于P(7,5),除一下就可以得到答案。
GGN的程序解决
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=20;
int dpc[maxn][maxn]; //储存组合数
int vis[maxn][maxn]; //记忆化搜索标记
int C(int n,int m) //记忆化搜索
{
if(vis[n][m])
return dpc[n][m];
vis[n][m]=1;
if(m==n || m==0)dpc[n][m]=1;
else if(n<m)dpc[n][m]=0;
else dpc[n][m]=C(n-1,m-1)+C(n-1,m);
return dpc[n][m];
}
int P(int n,int m) //暴力求排列
{
int ans=1;
for(int i=1;i<=m;i++)
ans*=(n-i+1);
return ans;
}
int f(int i) //至少对i道题的方案数
{
return C(5,i) * P(7-i,5-i);
}
int E() //计算有得分的方案数
{
int ans=0;
for(int i=1;i<=5;i++)
if(i&1)
ans+=f(i);
else ans-=f(i);
return ans;
}
int main()
{
cout<<"Method 1 "<<endl;
cout<<"E = "<<E()<<", all = "<<P(7,5)<<endl;
cout<<"P = "<<(double)E()/P(7,5)<<endl;
return 0;
}
这个程序的运行结果如下:
为了证明这个程序的正确性,写个暴力检验一下也是必不可少的:
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
int A[6],B[6]={-1,1,2,3,4,5}; //两个序列 1~7 表示七个选项
int vis[8];
int Cnt=0; //得分方案数
void DFS(int step) //暴力搜索
{
if(step>5) //得到一个解
{
for(int i=1;i<=5;i++) //判断是否暴零
if(A[i]==B[i])
{
Cnt++;break;
}
return;
}
for(int i=1;i<=7;i++) //枚举选项
{
if(!vis[i])
{
vis[i]=1;
A[step]=i;
DFS(step+1);
vis[i]=0;
}
}
}
int main()
{
cout<<"Method 2"<<endl;
DFS(1); //暴搜
cout<<"E = "<<Cnt<<" all = "<<2520<<endl;
cout<<"P = "<<(double)Cnt/2520<<endl;
return 0;
}
这个程序的运行效果如下:
发现得到了完全相同的答案,GGN很开心!这道题据HJQ大佬说还有一种DP的方法,不过我暂时还没有学会,所以没写在这里。
小结1
不难发现,当你在做七选五的时候,如果你乱蒙,你有52%的概率不得零分。大概就是一半的概率呗。但是你也别高兴得太早…
问题2
按照问题1的规则,如果我们定义一个回答的得分为
求随机选的得分期望是多少。
这道题好像后来没有人试着用数学方法去解,不过我试着写了个暴力..
#include<cstdio>
#include<algorithm>
#include<iostream>
using namespace std;
int A[6],B[6]={-1,1,2,3,4,5}; //两个序列 1~7 表示七个选项
int vis[8];
int Cnt[6]; //统计每一种得分的方案数
void DFS(int step) //暴力搜索
{
if(step>5) //得到一个解
{
int ans=0;
for(int i=1;i<=5;i++) //计算得分
if(A[i]==B[i])
ans++;
Cnt[ans]++; //方案数++
return;
}
for(int i=1;i<=7;i++) //枚举选项
{
if(!vis[i])
{
vis[i]=1;
A[step]=i;
DFS(step+1);
vis[i]=0;
}
}
}
void output(double x) //输出柱状图
{
for(int i=1;i<=x;i++)
printf("█"); //稍微有点简陋凑合看吧
double delta=x-int(x);
if(delta>=0.8)
printf("▉");
else if(delta>=0.7)
printf("▊");
else if(delta>=0.6)
printf("▋");
else if(delta>=0.5)
printf("▌");
else if(delta>=0.4)
printf("▍");
else if(delta>=0.2)
printf("▎");
else printf("▏");
}
int main()
{
cout<<"Question 2"<<endl;
DFS(1); //暴搜
long long score_sum=0;
for(int i=0;i<=5;i++)
{
printf("Score =%3d:%5d ",i,Cnt[i]);
output((double)Cnt[i]/50.0);
printf("\n");
score_sum+=Cnt[i]*i;
}
printf("Ex = %lf\n",(double)score_sum/2520);
return 0;
}
这个程序的运行结果:
由此可见,七选五随便选得分的期望只有0.7分,连一分都不到。
(由此观之,王之蔽甚矣)
结论
英语七选五,还是认认真真答吧,想靠蒙得分,没戏…