【BZOJ5094】硬盘检测 概率

【BZOJ5094】硬盘检测

Description

很久很久以前,小Q买了一个大小为n单元的硬盘,并往里随机写入了n个32位无符号整数。因为时间过去太久,硬盘上的容量字眼早已模糊不清,小Q也早已忘记了硬盘的容量。小Q记得,n可以被表示成10^k(1<=k<=7)的形式,即十到一千万。他还记得自己曾经m次随机读取某个32位无符号整数的记录。小Q现在正在Quapler宇宙飞船上遨游WF18星座,所以他想请你帮他找出n的具体大小。

Input

第一行包含一个正整数m(m=10000),表示随机访问硬盘的次数。

接下来m行,每行一个整数a_i(0<=a_i<2^{32}),即每次随机访问读取的结果。

Output

 输出一行一个整数,即n的大小。

题解:难道只有我把具体的概率算出来了吗。。。好多人都是分析数据+特判过的。

我们可以直接算出对于n的所有取值,出现给定情况的概率是多少。如果m个数中本质不同的有k个,每个出现的次数为c1,c2...ck,于是这种情况的概率就是:

$C_n^k imes C_m^{c_1} imes C_{m-c_1}^{c_2} imes C_{m-c_1-c_2}^{c_3} ... over n^m$

最后取概率最大的n即可。这种方法在n更大,可能的取值更多的情况下也能用。

但是组合数太大了没法存,怎么办?取log即可。

注意如果10^7预处理阶乘会TLE。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <map>
#include <cmath>

using namespace std;
int m,tot,ans;
double mx,sum;
map<int,int> s;
int cnt[10010];
double jc[10010];
inline double c(int a,int b)
{
	if(a<b)	return -1e10;
	return jc[a]-jc[a-b]-jc[b];
}
inline int rd()
{
	int ret=0,f=1;	char gc=getchar();
	while(gc<'0'||gc>'9')	{if(gc=='-')	f=-f;	gc=getchar();}
	while(gc>='0'&&gc<='9')	ret=ret*10+(gc^'0'),gc=getchar();
	return ret*f;
}
int main()
{
	m=rd();
	register int n,i,a,tmp;
	for(i=1;i<=m;i++)
	{
		a=rd();
		if(!s[a])	s[a]=++tot;
		cnt[s[a]]++;
	}
	mx=-1e10;
	for(i=1;i<=m;i++)	jc[i]=jc[i-1]+log(i);
	for(n=10;n<=10000000;n*=10)
	{
		if(n<tot)	continue;
		sum=0;
		for(i=1;i<=tot;i++)	sum+=log(n-i+1)-log(i);
		sum+=-log(n)*m,tmp=m;
		for(i=1;i<=tot;i++)	sum+=c(tmp,cnt[i]),tmp-=cnt[i];
		if(sum>mx)	mx=sum,ans=n;
	}
	printf("%d",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/CQzhangyu/p/8011316.html