「雅禮集訓 2018 Day10」足球大戰

題面

有一場足球比賽,還有nn秒就要結束了,比分還是0:00:0

主隊每秒進球概率爲pp,客隊每秒進球概率爲qq,求主隊獲勝概率。

注意,一秒鐘一個隊最多進一個球,主隊獲勝當且僅當主隊進球比客隊多。

爲了避免精度誤差,把最後的答案化成最簡分數xy\frac{x}{y},輸出xxyy關於(109+7)(10^9+7)的逆元的乘積即可。

根據費馬小定理

xy mod (109+7)=x×y109+5 mod (109+7)\frac{x}{y}\ mod \ (10^9+7)=x\times y^{10^9+5} \ mod \ (10^9+7)

ppqq將通過一種特別的方式給出:給出pa,pb,qa,qbpa,pb,qa,qbp=papb,q=qaqbp=\frac{pa}{pb},q=\frac{qa}{qb}

analysis


首先要得熟悉費馬小定理:
p,ap(a,p)=1,apa(modp)若p爲素數,a不是p的倍數或說成(a,p)=1,則a^p\equiv a\pmod p
這樣就可以用快速冪求逆元了
而題中那個式子其實就是在啓發我們使用費馬小定理來求單個數的逆元


分析題面可知,如果主隊要贏,那麼假設主隊進i顆球,那麼客隊就只能進少於i顆球

那麼主隊在n秒內進i顆球的概率可不可以算出來呢?相當於是一共有n顆球,主隊在其中選i顆球進(裏皮:還可以選球進??多少錢我來一發!),n-i顆球不進,那麼一共就有CniC_{n}^{i}種可能性能夠使得主隊進i顆球,那麼進i顆球的概率就是P=Cnipi(1p)niP=C_{n}^{i}p^i(1-p)^{n-i}

同理,假設客隊進j顆球,那麼概率就是P=Cnjqj(1q)njP=C_{n}^{j}q^j(1-q)^{n-j}

如果主隊要贏,那麼i>j,i>=j+1i>j,也就是i>=j+1,於是主隊贏的概率就是

P()=i=1nP(i)×j=0i1P(j)=i=1nCnipi(1p)ni×j=0i1Cnjqj(1q)nj P_{(主隊贏)}= \sum_{i=1}^{n}P_{(主隊進i顆球)}\times \sum_{j=0}^{i-1}P_{(客隊進j顆球)}\\=\sum_{i=1}^{n}C_{n}^{i}p^i(1-p)^{n-i}\times \sum_{j=0}^{i-1}C_{n}^{j}q^j(1-q)^{n-j}

根據數據範圍,現在的問題就是在給定p,qp,q的情況下O(n)O(n)求出這個式子的值

展開式子:

P=i=1nCnipi(1p)ni×j=0i1Cnjqj(1q)nj=Cn1p1(1p)n1×[Cn0q0(1q)n]+Cn2p2(1p)n2×[Cn0q0(1q)n+Cn1q1(1q)n1]+Cn3p3(1p)n3×[Cn0q0(1q)n+Cn1q1(1q)n1+Cn2q2(1q)n2]+Cnnpn(1p)nn×[Cn0q0(1q)n+Cnn1qn1(1q)n(n1)+Cnnqn(1q)nn] \begin{aligned} P&=\sum_{i=1}^{n}C_{n}^{i}p^i(1-p)^{n-i}\times \sum_{j=0}^{i-1}C_{n}^{j}q^j(1-q)^{n-j}\\ =&C_{n}^{1}p^1(1-p)^{n-1}\times [C_{n}^{0}q^0(1-q)^{n}]+\\ &C_{n}^{2}p^2(1-p)^{n-2}\times [C_{n}^{0}q^0(1-q)^{n}+C_{n}^{1}q^1(1-q)^{n-1}]+\\ &C_{n}^{3}p^3(1-p)^{n-3}\times [C_{n}^{0}q^0(1-q)^{n}+C_{n}^{1}q^1(1-q)^{n-1}+C_{n}^{2}q^2(1-q)^{n-2}]+\\ &\dots\\ &C_{n}^{n}p^n(1-p)^{n-n}\times [C_{n}^{0}q^0(1-q)^{n}\dots +C_{n}^{n-1}q^{n-1}(1-q)^{n-(n-1)}+C_{n}^{n}q^n(1-q)^{n-n}]\\ \end{aligned}

於是發現一個神奇的東西:

方括號裏面的東西可以O(1)遞推!

假設當前最外面是第i個和式,設方括號裏面的爲SiS_i,那麼

Si=Si1+Cni1qi1(1q)niS_i=S_{i-1}+C_{n}^{i-1}q^{i-1}(1-q)^{n-i}

然後最外面的p,(1p)p,(1-p)次數分別遞增和遞減
鑑於這題卡內存,我們就用一些變量來保存這些遞推的值(Si,q,(1q),p,(1p),inv(1q),inv(1p)S_i,q,(1-q),p,(1-p),inv(1-q),inv(1-p))

對於組合數,由公式得:

Cni=n!i!(ni)!C_{n}^{i}=\frac{n!}{i!(n-i)!}

由於只有分子在變,我們只需要預處理出1到n的階乘的逆元就可以O(1)算出組合數了

線性求逆元,可以先求一個1到n的逆元,然後在做一個前綴積就可以得到1到n的階乘的逆元,也可以直接求階乘逆元(不知爲什麼我第一種方法跑不過)

時間複雜度:O(n)

code

#include<bits/stdc++.h>
using namespace std;
#define loop(i,start,end) for(register int i=start;i<=end;++i)
#define clean(arry,num) memset(arry,num,sizeof(arry))
#define anti_loop(i,start,end) for(register int i=start;i>=end;--i)
#define ll long long
template<typename T>void read(T &x){
	x=0;char r=getchar();T neg=1;
	while(r>'9'||r<'0'){if(r=='-')neg=-1;r=getchar();}
	while(r>='0'&&r<='9'){x=(x<<1)+(x<<3)+r-'0';r=getchar();}
	x*=neg;
}
int n;
const int maxn=1e7+10;
#define mod (1000000007)
int pa,pb,qa,qb,njc;
int inv[maxn];
int ksm(int a,int x){
	int stag=a;
	int res=1;
	while(x){
		if(x&1)res=1LL*res*stag%mod;
		x>>=1;
		stag=1LL*stag*stag%mod;
	}
	return res%mod;
}
#define Qinv(x) ksm(x,mod-2)
inline int c(int a,int b){return ((1LL*njc*inv[a]%mod*inv[n-a]%mod)%mod);}
inline int XSub(int x,int y){return (mod+(x-y)%mod)%mod;}
inline int Inc(int x,int y){return (x%mod+y%mod)%mod;}
int main(){
	#ifndef ONLINE_JUDGE
	freopen("datain.txt","r",stdin);
	#endif
	read(n);
	read(pa);read(pb);read(qa);read(qb);
	int p=1LL*pa*Qinv(pb)%mod;
	int q=1LL*qa*Qinv(qb)%mod;
	clean(inv,0);inv[1]=inv[0]=1;
	njc=1;
	if(!p) return putchar('0'),0;
	if(!q) return printf("%d",XSub(1,ksm(XSub(1,p),n))),0;//特判p=0或q=0的情況
    if(!(p^1)) return printf("%d",XSub(1,ksm(q,n))),0;//特判p=1的情況

	loop(i,1,n) njc=1LL*njc*i%mod;
	inv[n]=Qinv(njc);
	for(register int i=n-1;~i;--i) inv[i]=1LL*inv[i+1]*(i+1)%mod;//預處理n!和階乘逆元

	int p1=1;//p1=>p^n
	int p2=ksm(XSub(1,p),n);//p2=>(1-p)^n
	int invp2=Qinv(XSub(1,p));//invp2=>(1-p2)^-1
	int q1=Qinv(q);//q1=>q^n
	int q2=ksm(XSub(1,q),n+1);//q2=>(1-q)^n
	int invq2=Qinv(XSub(1,q));//invq2=>(1-q2)^-1
	int sum=0;//sum=>sigma....
	int res=0;
	loop(i,1,n){
		p1=1LL*p1*p%mod;
		q1=1LL*q1*q%mod;
		q2=1LL*q2*invq2%mod;
		p2=1LL*p2*invp2%mod;
		sum=Inc(sum,1LL*q1*q2%mod*c(i-1,n)%mod);
		res=Inc(res,1LL*p1*p2%mod*sum%mod*c(i,n)%mod);
	}
	printf("%d\n",res);
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章