1. 程式人生 > 其它 >【模擬賽】遊戲

【模擬賽】遊戲

考慮 \(DP\)

如果兩人都miss,則沒有意義,所以有:

令轉移 \(A\) 的概率為:\(p_a=\frac{p(1-q)}{1-(1-p)(1-q)}\)

轉移 \(B\) 的概率為:\(p_b=\frac{q(1-p)}{1-(1-p)(1-q)}\)

轉移 \(C\) 的概率為:\(p_c=1-p_a-p_b\)

則:\(dp[i][j]=p_a\times dp[i-1][j]+p_b\times dp[i][j-1]+p_c\times dp[i-1][j-1]\)。直接 \(DP\)\(O(nm)\) 的。

因為轉移的概率與當前血量無關,於是我們可以用一個非常經典的套路,把整個 \(DP\)

想成一個序列,因為 \(B+C=m,A+C<n\) 。於是可以直接列舉走了多少個 \(C\),再用隔板法求出 \(B\) 的位置,最後乘上 \(A\) 的位置方案。而 \(BC\) 一共為 \(m\) 個,可以直接預處理放超過 \(k\)\(A\) 的方案數。

注意實現時處理到 \(Bob\) 量為1時即可,最後 \(Alice\) 一次打敗 \(Bob\)

code

#include<bits/stdc++.h>
using namespace std;
const int N=5e6,P=998244353,inv100=828542813;
int T,n,m,fac[N+5],inv[N+5],p,q,_p,_q;
int pa[N+5],pb[N+5],pc[N+5],sum[N+5];
int KSM(int a,int b)
{
	int ret=1;
	while(b)
	{
		if(b&1) ret=1ll*ret*a%P;
		a=1ll*a*a%P;b>>=1ll; 
	}
	return ret;
}
int C(int a,int b) {return 1ll*fac[a]*inv[b]%P*inv[a-b]%P;} 
int main()
{
	fac[0]=inv[0]=1;for(int i=1; i<=N; i++) fac[i]=1ll*fac[i-1]*i%P;
	inv[N]=KSM(fac[N],P-2);
	for(int i=N-1; i>=1; i--) inv[i]=1ll*(i+1)*inv[i+1]%P;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d%d%d",&n,&m,&p,&q);
		_p=100-p;_q=100-q;
		p=1ll*p*inv100%P;q=1ll*q*inv100%P;
		_p=1ll*_p*inv100%P;_q=1ll*_q*inv100%P;
		int hv=KSM((1-(1ll*_p*_q%P)+P)%P,P-2);
		pa[0]=pb[0]=pc[0]=sum[0]=1;
		pa[1]=1ll*_p*q%P*hv%P;
		pb[1]=1ll*p*_q%P*hv%P;
		pc[1]=1ll*p*q%P*hv%P;
		sum[1]=1ll*(sum[0]+1ll*C(m,1)*pa[1]%P)%P;
		for(int i=2; i<=max(n,m); i++)
			pa[i]=1ll*pa[i-1]*pa[1]%P,
			pb[i]=1ll*pb[i-1]*pb[1]%P,
			pc[i]=1ll*pc[i-1]*pc[1]%P,
			sum[i]=(sum[i-1]+1ll*C(m+i-1,i)*pa[i]%P)%P;
		//sum表示選擇不超過i個A的方案數。 
		int ans=0;
		for(int i=0; i<min(n,m); i++)//列舉C的個數
		{
			int tmp=1ll*pc[i]*pb[m-i-1]%P;
			ans+=1ll*tmp*C(m-1,i)%P*sum[n-i-1]%P,ans%=P;
		}
		printf("%d\n",1ll*p*hv%P*ans%P);
	}
	return 0;
}