英雄会(csdn pongo)题解之朋友的礼物

这道题第一次没提交通过,因为我直接截断了小数点后面的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),可以参考下。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值