这道题第一次没提交通过,因为我直接截断了小数点后面的8位,忘记四舍五入了。
朋友的礼物
题目详情:
n个人,每个人都有一件礼物想送给他人,他们决定把礼物混在一起,然后每个人随机拿走一件,问恰好有m个人拿到的礼物恰好是自己的概率是多少?
输出结果四舍五入,保留8位小数,为了保证精度,我们用字符串作为返回类型。
输入:n,m (0<n<100, 0<=m<=n)
例如:
n = 2,m = 1,输出:0.00000000;
n = 99,m = 0,输出:0.36787944
..
-----------------------------------------分割线----------------------------------------------
1.设f(n,m)为n个人中有m个人拿到自己礼物情况的次数,n个人一共有An,n=n!种情况,p(n,m)为n个人中有m个人拿到自己礼物情况的概率,
那么p(n,m)=f(n,m)/An,n=f(n,m)/n!
2.注意特殊情况:f(n,n-1)=p(n,n-1)=0 , f(1,1)=p(1,1)=1 , f(n,n)=1, p(n,n)=1/n!
3.Cm,n为从n中取m个的组合数,Am,n为其对应的排列
f(n,m)=Cm,n * (An-m,n-m - f(n-m,1) - f(n-m,2) - ... -f(n-m,n-m -2) - 1)
那么:
p(n,m)=f(n,m)/An,n
=Cm,n * ( An-m,n-m - f(n-m,1) - f(n-m,2) - ... -f(n-m,n-m -2) - 1 ) / n!
=Cm,n /Am,n * ( 1 - f(n-m,1)/An-m,n-m - f(n-m,2)/An-m,n-m - ... -f(n-m,n-m -2)/An-m,n-m - 1/An-m,n-m)
即p(n,m)=1/m! *(1- p(n-m,1) - p(n-m,2) - ... - p(n-m,n-m -2) -1/(n-m)! )
到这里,你发现递归了吧!!递归的出口就是:n==m return p(n,n)=1/n!
当然要提前计算好n!并且用map或hash_map保存中间计算的p(n,m)值,以减少重复计算,这样复杂度为O((n-m)^2)
4.注意结果需要四舍五入
5.C++代码如下
#include <stdio.h>
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <sstream>
#include <ctime>
using namespace std;
class Test {
public:
static std::vector<double> faVal;//1/n!
static std::map<std::vector<int>,double> gVal;
inline static void Factorial(int n){//1/n!
double res =1;
faVal.push_back(1);
faVal.push_back(1);
for(int i=2;i<=n;++i){
res/=i;
faVal.push_back(res);
}
}
static double p(int n,int m){
if(n==m)
return faVal[n];
std::vector<int> first;
first.push_back(n);
first.push_back(m);
std::map<std::vector<int>,double>::iterator it=gVal.find(first);
if(it!=gVal.end())
return it->second;
double res=faVal[m];
double tmp=1;
int nm=n-m;
for(int i=1;i<=nm-2;++i)
tmp-=p(nm,i);
double tmp1=faVal[nm];
tmp-=tmp1;
res*=tmp;
gVal.insert(std::make_pair(first,res));
return res;
}
static string calculate (int n,int m){
if(m==n&&m==1)//特殊情况
return "1.00000000";
bool firstTime=true;
if(firstTime){
firstTime=false;
Factorial(100);
}
std::stringstream ss;
double dRes=p(n,m);
int i=(int)(1000000000*dRes);//为了4舍5如多乘一位
int ys=i%10;
int jw=0;//四舍
if(ys>=5)//五入
jw=1;
//四舍五入前的小数点后前八位转换为字符串
i/=10;
ss<<i;
std::string res;
ss>>res;
while(res.size()<8){//不够8位,前补零
res='0'+res;
}
if(jw)//四舍五入
for(i=res.size()-1;i>=0;--i)
if(res[i]=='9')
res[i]='0';
else{
++res[i];
break;
}
//前面添加0和小数点
res="0."+res;
return res;
}
};
std::vector<double> Test::faVal;
std::map<std::vector<int>,double> Test::gVal;
//start 提示:自动阅卷起始唯一标识,请勿删除或增加。
int main(){
clock_t t1=clock();
cout<<Test::calculate(4,0)<<endl;
clock_t t2=clock();
std::cout<<(t2-t1)/(double)CLOCKS_PER_SEC<<"s"<<std::endl;
}
//end //提示:自动阅卷结束唯一标识,请勿删除或增加。
以上是我自己的解法,后来群里面有人发给我一个链接,才知道原来这是错排问题(见http://zh.wikipedia.org/wiki/%E9%94%99%E6%8E%92%E9%97%AE%E9%A2%98),可以参考下。