https://vjudge.net/problem/UVA-1639
某人喜欢吃糖,有两个罐子,每个罐子都里装了n块糖,每天他都要随机打开一个罐子吃一块糖,概率分别为$p$,$1-p$,直到有一天,他打开罐子后发现这个罐子里面没有糖了。问另外一个罐子里剩下的糖的数量的期望是多少。
数据范围:$1\leqslant n\leqslant 2\times 10^5$
以前看到这道题直接被吓到了,因为没有注意数据范围,所以直接就不去想枚举另外一个罐子里面剩下的糖的事情……
假设最后一次开的罐子是A,另外一个罐子剩余$k$个糖,那么概率是
\[p\cdot C(2n-k,n)(1-p)^{n-k}\cdot p^{n}\]
同理,若最后一次开的是B,另外一个罐子剩余$k$个糖,那么概率是
\[(1-p)\cdot C(2n-k,n)p^{n-k}\cdot (1-p)^{n}\]
所以期望就是
\[\sum_{k=0}^n k\times p\cdot C(2n-k,n)(1-p)^{n-k}\cdot p^{n}+\sum_{k=0}^n k\times (1-p)\cdot C(2n-k,n)p^{n-k}\cdot (1-p)^{n}\]
注意数据范围,因为数据变化比较大,计算的时候要注意尽量算同阶的,C可以达到$10^5!$,而p可以达到$10^{-6}^{10^5}$,都超出了double的范围,所以需要同时乘和除,比较麻烦……
另外一种办法是取对数,虽然很大,但是取了对数以后乘法变加法,$a*b^k$就会变成$k \ln(a+b)$,就不会超出范围,但是容易损失精度,题目要求的精度只有$10^{-4}$,因此也许可以使用这种方法……但是中间过程要用long double,直接double精度仍然过不了。
但是能降低编程复杂度,就算WA了一次也比调同时乘除快……
为了保险还是要练习码力啊,以后写个同时乘除的试下[快哭了]……
AC代码
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define REP(i,a,b) for(register int i=(a); i<(b); i++)
#define REPE(i,a,b) for(register int i=(a); i<=(b); i++)
#define PERE(i,a,b) for(register int i=(a); i>=(b); i--)
#ifdef sahdsg
#define DBG(...) printf(__VA_ARGS__)
#else
#define DBG(...) (void)0
#endif
#define ln log
using namespace std;
typedef long long LL;
int n;
long double p;
int kase=0;
long double jc[400007];
inline void db() {
jc[0]=0;
REP(i,1,400007) {
jc[i]=jc[i-1]+ln(i);
}
}
int main() {
db();
while(~scanf("%d%Lf", &n, &p)) {
long double lnp = ln(p), ln1p=ln(1-p);
long double ans=0;
REPE(k,1,n) {
ans+=k*exp(jc[2*n-k]-jc[n]-jc[n-k]+(n-k)*lnp+(n+1)*ln1p);
ans+=k*exp(jc[2*n-k]-jc[n]-jc[n-k]+(n-k)*ln1p+(n+1)*lnp);
}
printf("Case %d: %.6Lf\n", ++kase, ans);
}
return 0;
}
原文:https://www.cnblogs.com/sahdsg/p/11784322.html