「雅礼集训 2018 Day10」足球大战

题面

有一场足球比赛,还有 n n n秒就要结束了,比分还是 0 : 0 0:0 0:0

主队每秒进球概率为 p p p,客队每秒进球概率为 q q q,求主队获胜概率。

注意,一秒钟一个队最多进一个球,主队获胜当且仅当主队进球比客队多。

为了避免精度误差,把最后的答案化成最简分数 x y \frac{x}{y} yx,输出 x x x y y y关于 ( 1 0 9 + 7 ) (10^9+7) (109+7)的逆元的乘积即可。

根据费马小定理

x y   m o d   ( 1 0 9 + 7 ) = x × y 1 0 9 + 5   m o d   ( 1 0 9 + 7 ) \frac{x}{y}\ mod \ (10^9+7)=x\times y^{10^9+5} \ mod \ (10^9+7) yx mod (109+7)=x×y109+5 mod (109+7)

p p p q q q将通过一种特别的方式给出:给出 p a , p b , q a , q b pa,pb,qa,qb pa,pb,qa,qb p = p a p b , q = q a q b p=\frac{pa}{pb},q=\frac{qa}{qb} p=pbpa,q=qbqa

analysis


首先要得熟悉费马小定理:
若 p 为 素 数 , a 不 是 p 的 倍 数 或 说 成 ( a , p ) = 1 , 则 a p ≡ a ( m o d p ) 若p为素数,a不是p的倍数或说成(a,p)=1,则a^p\equiv a\pmod p p,ap(a,p)=1,apa(modp)
这样就可以用快速幂求逆元了
而题中那个式子其实就是在启发我们使用费马小定理来求单个数的逆元


分析题面可知,如果主队要赢,那么假设主队进i颗球,那么客队就只能进少于i颗球

那么主队在n秒内进i颗球的概率可不可以算出来呢?相当于是一共有n颗球,主队在其中选i颗球进(里皮:还可以选球进??多少钱我来一发!),n-i颗球不进,那么一共就有 C n i C_{n}^{i} Cni种可能性能够使得主队进i颗球,那么进i颗球的概率就是 P = C n i p i ( 1 − p ) n − i P=C_{n}^{i}p^i(1-p)^{n-i} P=Cnipi(1p)ni

同理,假设客队进j颗球,那么概率就是 P = C n j q j ( 1 − q ) n − j P=C_{n}^{j}q^j(1-q)^{n-j} P=Cnjqj(1q)nj

如果主队要赢,那么 i > j , 也 就 是 i > = j + 1 i>j,也就是i>=j+1 i>j,i>=j+1,于是主队赢的概率就是

P ( 主 队 赢 ) = ∑ i = 1 n P ( 主 队 进 i 颗 球 ) × ∑ j = 0 i − 1 P ( 客 队 进 j 颗 球 ) = ∑ i = 1 n C n i p i ( 1 − p ) n − i × ∑ j = 0 i − 1 C n j q j ( 1 − q ) n − j 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()=i=1nP(i)×j=0i1P(j)=i=1nCnipi(1p)ni×j=0i1Cnjqj(1q)nj

根据数据范围,现在的问题就是在给定 p , q p,q p,q的情况下 O ( n ) O(n) O(n)求出这个式子的值

展开式子:

P = ∑ i = 1 n C n i p i ( 1 − p ) n − i × ∑ j = 0 i − 1 C n j q j ( 1 − q ) n − j = C n 1 p 1 ( 1 − p ) n − 1 × [ C n 0 q 0 ( 1 − q ) n ] + C n 2 p 2 ( 1 − p ) n − 2 × [ 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 × [ 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 ] + … C n n p n ( 1 − p ) n − n × [ C n 0 q 0 ( 1 − q ) n ⋯ + C n n − 1 q n − 1 ( 1 − q ) n − ( n − 1 ) + C n n q n ( 1 − q ) n − n ] \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} P==i=1nCnipi(1p)ni×j=0i1Cnjqj(1q)njCn1p1(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]

于是发现一个神奇的东西:

方括号里面的东西可以O(1)递推!

假设当前最外面是第i个和式,设方括号里面的为 S i S_i Si,那么

S i = S i − 1 + C n i − 1 q i − 1 ( 1 − q ) n − i S_i=S_{i-1}+C_{n}^{i-1}q^{i-1}(1-q)^{n-i} Si=Si1+Cni1qi1(1q)ni

然后最外面的 p , ( 1 − p ) p,(1-p) p,(1p)次数分别递增和递减
鉴于这题卡内存,我们就用一些变量来保存这些递推的值( S i , q , ( 1 − q ) , p , ( 1 − p ) , i n v ( 1 − q ) , i n v ( 1 − p ) S_i,q,(1-q),p,(1-p),inv(1-q),inv(1-p) Si,q,(1q),p,(1p),inv(1q),inv(1p))

对于组合数,由公式得:

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

由于只有分子在变,我们只需要预处理出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;
}

### 关于雅礼集训 2017 Day1 的题目及解析 #### 题目概述 根据已知引用内容[^3],雅礼集训 2017 Day1 的核心问题是关于矩阵操作的优化问题。给定一个 \(n \times m\) 的字符矩阵,其中 `#` 表示黑色格子,`.` 表示白色格子。目标是最小化将整个矩阵变为全黑所需的步数。 --- #### 解析与算法思路 ##### 输入描述 输入的第一行为两个整数 \(n\) 和 \(m\),分别代表矩阵的行数和列数。接下来 \(n\) 行每行包含长度为 \(m\) 的字符串,表示矩阵的内容。 ##### 输出描述 输出最小的操作次数使得整个矩阵变成全是黑色格子的状态。如果没有可行方案,则输出 `-1`。 --- ##### 算法设计 1. **可行性判断** 如果初始矩阵没有任何黑色格子 (`#`) 存在,则无法通过任何有限次操作使矩阵变黑,因此直接返回 `-1`[^4]。 2. **计算最少步数** 对于每一行,定义两种可能的操作方式: - 将该行全部涂黑。 - 不改变该行状态,仅依赖后续列操作来覆盖剩余白格。 同样地,对于每一列也存在类似的策略选择。最终的目标是综合考虑行列操作的影响,找到全局最优解。 3. **动态规划或贪心求解** 使用简单的遍历方法统计各行列中的黑白分布情况,并基于此决定最佳行动顺序。特别注意边界条件处理以及特殊情况下的额外开销评估。 ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 1e3 + 5; int n, m, h[MAXN], l[MAXN]; char s[MAXN][MAXN]; int main(){ cin >> n >> m; bool has_black = false; for (int i = 1; i <= n; ++i){ cin >> (s[i]+1); for (int j = 1; j <= m; ++j){ if (s[i][j] == '#'){ has_black = true; h[i]++; l[j]++; } } } if (!has_black){ cout << "-1"; return 0; } int res = INT_MAX; for (int i = 1; i <= n; ++i){ res = min(res, m - h[i] + !l[i]); } int extra_cost = 0; for (int j = 1; j <= m; ++j){ if (l[j] != n) extra_cost += 1; } cout << res + extra_cost; } ``` 上述代码实现了基本逻辑框架,包括读取数据、初步分析是否存在解决方案的可能性以及最后一步汇总总成本的过程[^3]。 --- #### 复杂度分析 时间复杂度主要取决于两次嵌套循环扫描整个矩阵所需的时间量级 O(n*m),空间消耗同样维持在线性范围内 O(n+m)。 --- #### 注意事项 - 当前实现假设所有测试实例均满足合理范围内的尺寸规格;实际应用时需增加更多健壮性的错误检测机制。 - 结果验证阶段应充分考虑到极端情形比如完全空白或者满布障碍物等情况是否被妥善处置。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AndrewMe8211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值