P2118【概率】Bug搜集 | |
|
问题描述
伊万是一个软件评测员,他喜欢查找软件中的Bug(错误或漏洞)。每当伊万拿到一个新的程序,首先他会将该程序中可能出现的Bug分成n类,每天伊万总能找到一个程序中的Bug。
如果在一个程序中,伊万找到了所有n种类型的Bug,伊万会称这个程序为“恶心程序”,并把评测结果放到他的博客上,告诉网友们不要使用该程序。
今天,伊万拿到了一个TEK的新程序,该程序非常复杂,它由s个子程序构成。如果在所有s个子程序中都去查找n种Bug,花的时间就太长了。于是伊万决定:如果在每个子程序中都至少找出一个Bug,并且在整个TEK程序中,n种Bug每种都至少被找到了一次,那么伊万就认定这个TEK程序是"恶心程序"。
每种Bug在程序中出现的概率都是相同的,同时,同种Bug在每个子程序中出现的概率也是相同的。该程序中的Bug数量几乎是无穷多,也就是说同一种Bug可能在同一个子系统中多次被伊万发现,并且每次的概率都是相同的。
请求出伊万定义TEK为“恶心程序”所需最少时间
输入格式
两个整数n和s(0 < n, s <= 1 000).
输出格式
一个实数,表示伊万所需最少时间,保留10个小数位
样例输入
1 2
样例输出
3.0000000000
来源 Northeastern Europe 2004
#include<iostream>
#include<cstdio>
using namespace std;
int n,s;
double f[1005][1005];
int main()
{
scanf("%d%d",&n,&s);
f[s][n]=0;
for(int i=s;i>=0;i--)
for(int j=n;j>=0;j--)
if(!(i==s&&j==n))f[i][j]=(s*n+f[i][j+1]*(n-j)*i+f[i+1][j]*j*(s-i)+f[i+1][j+1]*(n-j)*(s-i))/(s*n-i*j);
printf("%.10lf\n",f[0][0]);
return 0;
}
这道题,和NKOI的吸血鬼那道题很像,只是后者只需要开一维数组,而前者需要开二维数组f[i][j]同时记录第几i个子程序已经检查出Bug并且已经检查出了j种Bug的状态下,离终点还期望需要多少天。
易知,f[s][n]==0,而要利用递推关系,我们需要知道f[i][j]可以由那几个状态递推而来。
显然,对于f[i][j],有四种情况影响它的值。
1、f[i+1][j]:第二天选出一个Bug,这个Bug出现在新的子程序中(旧的子程序是指已经找出了Bug的i个子程序),但是这个Bug属于旧Bug(旧的Bug是指已经出现过的j种Bug),而这种情况出现的概率p1==((s-i)/s)*(j/n)
2、f[i][j+1]:思路同上,p2==(i/s)*((n-j)/n)
3、f[i][j+1]:思路同上,p3==((s-i)/s)*((n-j)/n)
4、f[i][j]:思路同上;p4==(i/s)*(j/n)
f[i][j]=p1*f[i+1][j]+p2*f[i][j+1]+p3*f[i+1][j+1]+p4*f[i][j]
移项即得到f[i][j]的表达式。
最后,因为要先计算f[i+1][j],f[i][j+1],f[i+1][j+1],所以循环倒着写。
不用担心越界问题,比如计算到f[s+1][n+1],不可能出现这种状态,而因为默认其初始值为零,所以在计算时此项为零,不需要担心(注意做除法的时候分母不为零就行)。