#N0258. 诚【NOIP2023模拟赛T3】

#N0258. 诚【NOIP2023模拟赛T3】

长文预警,如果是OIer想看题解请自行在目录部分找到对应部分跳过

然而显然这篇文章不只是给OIer写的

只是希望大家能感受一下信竞的门槛

顺便巩固一下知识点

题目描述

P P P(NOT PYZ)位于数轴的 x = 0 x=0 x=0 处。规定数轴右端为正方向。

每个时刻,小 P P P 会选择随机选择向左或向右走一步。形式化地,设小 P P P 目前在 x = p x=p x=p,小 P P P 会随机走到 x = p − 1 x=p−1 x=p1 x = p + 1 x=p+1 x=p+1。其中,向左走的概率为 a a + b \frac {a}{a+b} a+ba​,向右走的概率为 b a + b \frac {b}{a+b} a+bb​。

已知小 P P P 走了 m m m 步,求小 P P P 到达过 x = n x=n x=n 的概率。

答案对 998244353 998244353 998244353 取模。

输入格式

一行四个整数 n , m , a , b n,m,a,b n,m,a,b

输出格式

一行一个整数 x x x,表示答案。

可以证明,此概率一定为有理数。设概率为 p q \frac {p}{q} qp​,可以证明 q ≢ 0 ( m o d 998244353 ) q\not\equiv 0\pmod{998244353} q0(mod998244353),输出的 x x x 需满足 x = p × q − 1   m o d   998244353 x=p\times q^{-1}\bmod 998244353 x=p×q1mod998244353,或者说 x q ≡ p ( m o d 998244353 ) xq\equiv p\pmod {998244353} xqp(mod998244353)

要求 x ∈ [ 0 , 998244353 ) x\in[0,998244353) x[0,998244353)

输入数据 1

2 4 1 2

输出数据 1

12324005

输入数据 2

114 514 1919 810

输出数据 2

953191643

样例解释

向左走概率为 1 3 \frac {1}{3} 31​,向右走概率为 2 3 ​ \frac {2}{3​} 3​2

L L L 为向左走, R R R 为向右走。

有三种情况: R R 、 R L R R 、 L R R R RR、RLRR、LRRR RRRLRRLRRR

答案为 4 9 + 8 81 + 8 81 = 52 81 \frac {4}{9}+\frac {8}{81}+\frac {8}{81}=\frac{52}{81} 94+818+818=8152​。

可以自行运算验证 81 × 12324005 ≡ 52 ( m o d 998244353 ) 81\times 12324005\equiv 52\pmod {998244353} 81×1232400552(mod998244353)

故答案为 12324005 12324005 12324005

数据范围

对于 20 % 20\% 20% 的数据, 1 ≤ m ≤ 1 0 3 1\le m\le 10^3 1m103

对于 70 % 70\% 70% 的数据, 1 ≤ m ≤ 5 × 1 0 5 1\le m\le 5\times 10^5 1m5×105

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ m ≤ 1 0 7 , 1 ≤ a , b < 998244353 , a + b ≢ 0 ( m o d 998244353 ) 1\le n\le m\le 10^7,1\le a,b<998244353,a+b\not\equiv 0\pmod{998244353} 1nm1071a,b<998244353a+b0(mod998244353)

我们,从零开始 我们,从零开始 我们,从零开始

模运算意义下的 加、减、乘 操作

如果我要对一个整数进行足够多的加,减,乘操作,最后输出答案对 p p p取模后的结果,要求中途的中间运算结果不能超过 p p p,能不能做到?

这个问题对计算机是很现实的问题,计算机的整形数最大只能存 16 B y t e 16Byte 16Byte(cpp环境下),所以需要计算期间数都不能太大。

如果你数学还不错,那么你一定知道以下几个公式:

如果 a + b ≡ c   m o d   p 如果a+b \equiv c \bmod p 如果a+bcmodp
那么 a ≡ d   m o d   p , b ≡ e   m o d   p 那么a \equiv d \bmod p,b \equiv e \bmod p 那么admodp,bemodp
则 d + e ≡ c   m o d   p 则 d+e \equiv c \bmod p d+ecmodp

数学语言的是真够难受的,用计算机语言表示,就是(a+b)%p=(a%p+b%p)%p
(这里的%表示取模运算,是一个二元运算符,返回其前一个数对后一个数取余的结果)
用人话说,算两个数的 和之模,可以算其 模之和之模

怎么证明呢?实则不难,过程如下:

记 a = k a ⋅ p + b a ( k a , b a ∈ Z , b a < p ) 记a=k_a\cdot p+b_a(k_a,b_a \in \Z,b_a < p) a=kap+ba(ka,baZ,ba<p)
b = k b ⋅ p + b b ( k b , b b ∈ Z , b b < p ) b=k_b\cdot p+b_b(k_b,b_b \in \Z,b_b < p) b=kbp+bb(kb,bbZ,bb<p)
则实际上, a ≡ b a   m o d   p , b ≡ b b   m o d   p 则实际上,a\equiv b_a \bmod p,b\equiv b_b \bmod p 则实际上,abamodp,bbbmodp
a + b = k a ⋅ p + b a + k b ⋅ p + b b ≡ b a + b b ( m o d p ) a+b=k_a\cdot p+b_a+k_b\cdot p+b_b\equiv b_a+b_b\pmod p a+b=kap+ba+kbp+bbba+bb(modp)
所以 b a + b b ≡ c ( m o d p ) 所以b_a+b_b\equiv c\pmod p 所以ba+bbc(modp)
即得证

其实,如果直接把 ≡ \equiv 叫作同余号,上面也不用证明了。。。

减法的话,完全一样。差之模等于模之差之模。

当然,计算机中为避免模运算的负数结果,经常用这样的技巧:(a-b)%p==(a%p+p-b%p)%p

那乘法呢?

公式一模一样:

如果 a × b ≡ c   m o d   p 如果a\times b \equiv c \bmod p 如果a×bcmodp
那么 a ≡ d   m o d   p , b ≡ e   m o d   p 那么a \equiv d \bmod p,b \equiv e \bmod p 那么admodp,bemodp
则 d × e ≡ c   m o d   p 则 d\times e \equiv c \bmod p d×ecmodp

计算机语言:(a*b)%p=((a%p)*(b%p))%p

当然,由于计算机优先级的缘故,代码可以直接写成(a*b)%p=a%p*b%p

首先把 a × b a\times b a×b看成 b b b a a a相加,那套用上面公式, a a a可以用 a % p a\%p a%p替代;

再把 a % p × b a\%p\times b a%p×b看成 a % p a\%p a%p b b b相加,一样, b b b可以用 b % p b\%p b%p替代。

得证。积之模与模之积同余。

很好,如果计算机要计算一系列加、减、乘运算并且最后结果对 p p p取模,那么中途的中间运算结果对 p p p取模以便存储后在进行下一次运算是对答案没有影响的。

典型的,计算 a b   m o d   p a^b\bmod p abmodp时,当 p p p很大时,中间结果不可能存下。所以此时对中途数据取模后存储就显得尤为重要。

模运算意义下的 除(乘法逆元) 操作

我们在计算某些方案数时,有时会用到组合数。这些数通常来说都极大,需要你输出对 p p p取模的结果。

组合数的计算公式如下:

C n m = n ! m ! ( n − m ) ! C_n^m=\frac{n!}{m!(n-m)!} Cnm=m!(nm)!n!

对于大数阶乘,因为阶乘只有乘法运算,我们可以预处理阶乘对 p p p取余的结果。

但是,要计算组合数的时候,我们能直接用一个取模后的结果除以一个取模后的结果来得到新的取模后的结果吗?

我们用上面类似的方法尝试证明,发现做不到。

好消息是,这样做确实是错的。

尝试举例:

18 ≡ 4   m o d   7 18\equiv4\bmod7 184mod7

3 ≡ 3   m o d   7 3\equiv3\bmod7 33mod7

( 18 ÷ 3 ) = 6 ≡ 6   m o d   7 (18\div3)=6\equiv 6\bmod 7 (18÷3)=66mod7

而模之商: 4 3 \frac 43 34

(甚至不是整数啊!)

坏消息是,那我们怎么计算大数组合数对固定模数取余的结果呢?

好消息是:对于特殊情况,有办法。

逆元正题

我们记 a a a的乘法逆元为 a − 1 a^{-1} a1

定义逆元为,当 a × a − 1 ≡ 1 ( m o d p ) a\times a^{-1}\equiv1\pmod p a×a11(modp),我们就称 a − 1 a^{-1} a1 a a a在模 p p p意义下的的逆元,此时 a − 1 a^{-1} a1的作用就和 1 a \frac{1}{a} a1一样,能从原数中去除 a a a这个约数。

显然,乘法是可以在模意义下合法存在的,所以,模意义下的不能进行的除法运算被我们转化成了可以中途取模的乘法运算。

当然,其实两个数不是整除也可以用,因为,毕竟,可以先欠着嘛,以后再有再还,没有就算了。

所以一个有理数 p q \frac pq qp就也有其模 p p p意义下的整数值,计算方法为 p ⋅ q − 1   m o d   p p\cdot q^{-1}\bmod p pq1modp

那,有乘法逆元这个好东西让我们可以免去除法运算了,那一个数的乘法逆元怎么求呢?

§ \S §费马小定理

p p p为质数时,有
a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp)
等价命题为
a p ≡ a ( m o d p ) a^{p}\equiv a\pmod p apa(modp)
可以用归纳法进行证明。

那,什么是归纳法?

§ \S §归纳法

对于可数命题集 S S S,如果证明了 S 1 S_1 S1成立,又证明了对于任意的 n ∈ Z + n\in\Z^{+} nZ+ S n S_n Sn成立时 S n + 1 S_{n+1} Sn+1都成立,那么 ∀ n ∈ Z + \forall n\in\Z^{+} nZ+ S n S_n Sn都成立.

用人话说,如果你能证明 n = 1 n=1 n=1时某个命题成立,还能证明能证明某个命题成立它的下一个命题成立,那这些命题都成立,因为

道生一,一生二,二生三,三生万物 ——道德经

命题一显然成立,则为 道生,剩下的,自然全部顺推成立。

鸣谢:感谢我国古代著名数学家老子对数学界的重大贡献!

给个小例题吧,用归纳法证明 1 + 2 + ⋯ + n = n ( n + 1 ) 2 1+2+\cdots + n=\frac{n(n+1)}{2} 1+2++n=2n(n+1)

别笑! 你现在觉的归纳法多余,以后才知道是真香。

1 + 2 + ⋯ + n = f ( n ) 1+2+\cdots+n=f(n) 1+2++n=f(n),证明 f ( n ) = n ( n + 1 ) 2 f(n)=\frac{n(n+1)}{2} f(n)=2n(n+1)

f ( 1 ) = 1 ( 1 + 1 ) 2 f(1)=\frac{1(1+1)}{2} f(1)=21(1+1)显然成立

假设 f ( n ) = n ( n + 1 ) 2 f(n)=\frac{n(n+1)}{2} f(n)=2n(n+1)成立,证明 f ( n + 1 ) = ( n + 1 ) ( n + 2 ) 2 f(n+1)=\frac{(n+1)(n+2)}{2} f(n+1)=2(n+1)(n+2)成立。
( n + 1 ) ( n + 2 ) 2 = n 2 + 3 n + 2 2 \frac{(n+1)(n+2)}{2}=\frac{n^2+3n+2}{2} 2(n+1)(n+2)=2n2+3n+2
= n 2 + n + 2 n + 2 2 =\frac{n^2+n+2n+2}{2} =2n2+n+2n+2
= n 2 + n 2 + 2 n + 2 2 =\frac{n^2+n}{2}+\frac{2n+2}{2} =2n2+n+22n+2
= n ( n + 1 ) 2 + n + 1 =\frac{n(n+1)}{2}+n+1 =2n(n+1)+n+1
= f ( n ) + n + 1 =f(n)+n+1 =f(n)+n+1
= f ( n + 1 ) =f(n+1) =f(n+1)

证得 1 + 2 + ⋯ + n = n ( n + 1 ) 2 1+2+\cdots + n=\frac{n(n+1)}{2} 1+2++n=2n(n+1)

§ \S §二项式定理

补充前置芝士

公式:
( a + b ) n = ∑ i = 0 n ( ( i n ) × a i ⋅ b n − i ) (a+b)^n=\sum_{i=0}^{n} \left ( \binom{i}{n}\times a^i\cdot b^{n-i} \right ) (a+b)n=i=0n((ni)×aibni)
注: ( m n ) \binom{m}{n} (nm)就是组合数,相当于 C n m C_n^m Cnm

上式在 n = 2 n=2 n=2时就是我们最熟悉的完全平方公式, n = 3 n=3 n=3就是完全立方公式。

怎么证呢?

非常滴简单。

把上式拆开来写:

( a + b ) ( a + b ) ⋯ ( a + b ) (一共 n 个相乘) = C n n a n b 0 + C n n − 1 a n − 1 b 1 + ⋯ + C n 1 a 1 b n − 1 + C n 0 a 0 b n (a+b)(a+b)\cdots(a+b)(一共n个相乘)=C_n^na^nb^0+C_n^{n-1}a^{n-1}b^1+\cdots+C_n^1a^1b^{n-1}+C_n^0a^0b^n (a+b)(a+b)(a+b)(一共n个相乘)=Cnnanb0+Cnn1an1b1++Cn1a1bn1+Cn0a0bn

我们计算左边的时候,是将每一项 ( a + b ) (a+b) (a+b)中选一个 a a a b b b乘起来加到结果里,比如我有 i i i项选了 a a a,剩下 n − i n-i ni项选了 b b b,最终乘起来得到一项 a i b n − i a^ib^{n-i} aibni;由于我在 n n n项里选 i i i项来选 a a a ( i n ) \binom{i}{n} (ni)种选法,所以最后 a i b n − i a^ib^{n-i} aibni这一项会得到 C n i C_n^i Cni项,所以有一个组合数系数,原式也就一目了然了。

§ \S §证明费马小定理

p p p为质数时,有
a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp)
等价命题为
a p ≡ a ( m o d p ) a^{p}\equiv a\pmod p apa(modp)

证明:

对于任意质模数 p p p,当 a = 1 a=1 a=1时等式显然成立(这总能显然了吧。。。

证明 ∀ a ∈ Z + \forall a\in \Z^+ aZ+,如果 a p ≡ a ( m o d p ) a^{p}\equiv a\pmod p apa(modp),那么 ( a + 1 ) p ≡ a + 1 ( m o d p ) (a+1)^{p}\equiv a+1\pmod p (a+1)pa+1(modp)

( a + 1 ) p (a+1)^{p} (a+1)p进行二项式展开:
( a + 1 ) p = ∑ i = 0 p ( ( i p ) × a i ⋅ 1 n − i ) (a+1)^{p}=\sum_{i=0}^{p} \left ( \binom{i}{p}\times a^i\cdot 1^{n-i} \right ) (a+1)p=i=0p((pi)×ai1ni)
= ∑ i = 0 n ( ( i p ) × a i ) =\sum_{i=0}^{n} \left ( \binom{i}{p}\times a^i\right ) =i=0n((pi)×ai)
对于首相和末项,我们单独提出来:
= ( 0 p ) × a 0 + ( p p ) × a p + ∑ i = 1 p − 1 ( ( i p ) × a i ) =\binom{0}{p}\times a^0+\binom{p}{p}\times a^p+\sum_{i=1}^{p-1} \left ( \binom{i}{p}\times a^i\right ) =(p0)×a0+(pp)×ap+i=1p1((pi)×ai)
如果你学过组合数,就知道 ( 0 p ) \binom{0}{p} (p0) ( p p ) \binom{p}{p} (pp)都是1;

如果上过初中,就知道 a ≠ 0 a\not =0 a=0时, a 0 = 1 a^0=1 a0=1。当然要用洛必达解析延拓什么都是后话了。

所以进一步化简:

= 1 + a p + ∑ i = 0 p ( ( i p ) × a i ) =1+a^p+\sum_{i=0}^{p} \left ( \binom{i}{p}\times a^i\right ) =1+ap+i=0p((pi)×ai)

这三部分,我们利用模意义下的加法成立,拆开分别对 p p p进行取余:

  • 第一部分: 1 ≡ 1 ( m o d p ) 1\equiv 1 \pmod p 11(modp)

  • 第二部分:利用假设 a p ≡ a ( m o d p ) a^p\equiv a \pmod p apa(modp)

  • 第三部分:要观察和式中每一项的性质。

对于 C p i C_p^i Cpi,展开成阶乘的形式为 p ! i ! ( p − i ) ! \frac{p!}{i!(p-i)!} i!(pi)!p!

因为和式拆出了首项末项,此时 i i i的取值范围为 0 < i < p 00<i<p

所以 p − i p-i pi的取值范围为 0 < p − i < p 00<pi<p

发现它们的值都小于 p p p

返回阶乘式,发现,分子中的 p ! p! p!中一定有因数 p p p,而分母中的两个阶乘里所有乘数都是小于 p p p的,而 p p p又是质数,所以分母无法用多个乘数凑出一个 p p p,所以整个分母不包含因数 p p p,所以每一项 ( i p ) × a i \binom{i}{p}\times a^i (pi)×ai都是 p p p的倍数,即 ∀ i ∈ ( 0 , p ) ∩ Z , p ∣ C p i \forall i\in(0,p)\cap\Z,p|C_p^i i(0,p)Z,pCpi

那么既然每一项 ( i p ) × a i ≡ 0 ( m o d p ) \binom{i}{p}\times a^i\equiv 0\pmod p (pi)×ai0(modp),那么第三部分 ∑ i = 0 p ( ( i p ) × a i ) ≡ 0 ( m o d p ) \sum_{i=0}^{p} \left ( \binom{i}{p}\times a^i\right )\equiv 0\pmod p i=0p((pi)×ai)0(modp)便一目了然了。

所以三部分合起来:
1 + a p + ∑ i = 0 p ( ( i p ) × a i ) 1+a^p+\sum_{i=0}^{p} \left ( \binom{i}{p}\times a^i\right ) 1+ap+i=0p((pi)×ai)
≡ 1 + a ( m o d p ) \equiv 1+a \pmod p 1+a(modp)

Q . E . D . Q.E.D. Q.E.D.

再抄一遍费马小定理:

p p p为质数时,有 a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp)

那这说明什么呢?

§ \S §乘法逆元

如果你真心讨厌数学,或者数学巨佬,也可以跳过以上证明来到这一步。

由费马小定理得,当 p p p为质数时,有 a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1\pmod p ap11(modp)

a × a p − 2 ≡ 1 ( m o d p ) a\times a^{p-2}\equiv 1\pmod p a×ap21(modp)

根据乘法逆元定义:

定义逆元为,当 a × a − 1 ≡ 1 ( m o d p ) a\times a^{-1}\equiv1\pmod p a×a11(modp),我们就称 a − 1 a^{-1} a1 a a a在模 p p p意义下的的逆元

显然,这里的 a p − 2 a^{p-2} ap2就是 a a a在模 p p p意义下的逆元,

所以在模 p p p意义下, a − 1 = a p − 2 a^{-1}=a^{p-2} a1=ap2

很好,对于固定质模数 p p p,我们都能求出其逆元。

到这里,由于良心的出题人总是把模数设为一个大质数,我们不用担心没有逆元的情况。

§ \S §利用乘法逆元用整数表示模意义下的有理数

题目里也提到了, p q ( m o d p ) \frac pq\pmod p qp(modp)可以表示成 p × q − 1 ( m o d p ) p\times q^{-1}\pmod p p×q1(modp)

那在计算概率时,这样的整数形式的有理数能像朴素的有理数一样支持加,减,乘的操作吗?

答案是:完全可以

证明:

乘法最简单:
a b × c d = a × b − 1 × c × d − 1 = a × c × b − 1 × d − 1 = a c b d = a c b d \frac ab\times\frac cd=a\times b^{-1}\times c\times d^{-1}=a\times c\times b^{-1}\times d^{-1}=\frac{\frac{ac}{b}}{d}=\frac {ac}{bd} ba×dc=a×b1×c×d1=a×c×b1×d1=dbac=bdac
加减同理:
a b + c d = a × b − 1 + c × d − 1 \frac ab+\frac cd=a\times b^{-1}+c\times d^{-1} ba+dc=a×b1+c×d1
= a × d × b − 1 × d − 1 + c × b × d − 1 × b − 1 =a\times d\times b^{-1}\times d^{-1}+c\times b\times d^{-1}\times b^{-1} =a×d×b1×d1+c×b×d1×b1
= ( a × d + c × b ) × d − 1 × b − 1 =(a\times d+c\times b)\times d^{-1}\times b^{-1} =(a×d+c×b)×d1×b1
= a × d + c × b d × b =\frac{a\times d+c\times b}{d\times b} =d×ba×d+c×b

所以,就放心大胆的算吧!

【OI】 O ( n ) O(n) O(n)预处理组合数

慢慢来,一步一步优化。

const int MAXN=1e7+5,p=998244353;
int fac(int x){//计算阶乘
    int ret=1;
    for(int i=x;i>=1;i--){
        ret=ret*i%p;
    }
    return ret;
}
int pow(int base,int exp){//计算乘方
    int ret=1;
    for(int i=0;i<exp;i++){
        ret=ret*base%p;
    }
    return ret;
}
int inv(int x){//计算逆元
    return pow(x,p-2);
}
int C(int n,int m){
    return fac(n)*inv(fac(m))%p*inv(fac(n-m))%p;
}

这段 O ( n + p ) O(n+p) O(n+p)的代码大概几秒就跑完了。

不过,它只能计算一个组合数。

所以我们要考虑预处理。

我们发现,每次算一个大数阶乘的时候,会把前面所有数的阶乘全算出来,然而这些却没有得到利用,所以我们最好把他们存起来,换句话说,我们可以预处理掉所有的阶乘;我们还发现,逆元永远算的是一个数阶乘的逆元,所以我们可以顺便的把每个数阶乘的逆元预处理出来。

const int MAXN=1e7+5,p=998244353;
int pow(int base,int exp){//计算乘方
    int ret=1;
    for(int i=0;i<exp;i++){
        ret=(ret*base)%p;
    }
    return ret;
}
int fac[MAXN],inv[MAXN];
void init(){
    fac[0]=1;//此处定义0的阶乘为1
    for(int i=1;i<MAXN;i++){
        fac[i]=fac[i-1]*i%p;
        inv[i]=pow(fac[i],p-2);
    }
}
int C(int n,int m){
    return fac[n]*inv[m]%p*inv[n-m]%p;
}

这段 O ( n p ) O(np) O(np)的代码大概三年就跑完了,不过它处理出了 1 0 7 10^7 107内的所有阶乘及其逆元,使得组合数可以 O ( 1 ) O(1) O(1)询问求解,是个好的开始。

我们发现,那个 O ( p ) O(p) O(p)的复杂度,是那个笨拙的乘方函数贡献的。

那那个乘方函数能不能加加速呢?

【OI】快速幂

当然可以。

想当年,父亲教我编写从一加到一百的程序:

int ans=0;
for ( int i = 1; i <= 100; i++ ){
    ans = ans + i;
}
printf("%d",ans);

我想,这不就是个等差数列求和的事吗?有公式的,我都能口算的。

int n=100;
printf("%d",n*(n+1)/2);

父亲说,对,这就是算法的优化。

(我突然联想到了《背影》)

——我那时真是聪明过分,总觉他说话不大漂亮,非自己插嘴不可,但他终于讲定了价钱;就送我上车。他给我拣定了靠车门的一张椅子;我将他给我做的紫毛大衣铺好座位。他嘱我路上小心,夜里要警醒些,不要受凉。又嘱托茶房好好照应我。我心里暗笑他的迂;他们只认得钱,托他们只是白托!而且我这样大年纪的人,难道还不能料理自己么?我现在想想,我那时真是太聪明了。


现在,我们的任务是在指数很大的情况下快速求解 a b a^b ab

考虑 a b a^b ab的意义,即是 b b b a a a的连乘:

a b = a × a × ⋯ × a ⏞ b 个 a a^b= \overbrace{a \times a \times \cdots \times a }^{b个a} ab=a×a××a ba

  • b b b为偶数,我们就先计算出前一半的 a a a相乘后的结果,再将结果平方一下即可,平方对于计算机只不过是一个乘法的事。
  • b b b为奇数,我们就先计算出 a b − 1 a^{b-1} ab1,乘上 a a a即可。

计算机是可以递归求解的,上述文字可以很快转换成以下代码:

int qpow(int base,int exp){
    if(exp==0)return 1;
    int half=qpow(base,exp/2);
    if(exp%2==1){
        return half*half%p*base%p;
    }
    else {
        return half*half%p;
    }
}

这个方法的复杂度是多少呢?

我们考虑每次我们取当前要计算的部分的前一半进行递归,那么这个b能被折半多少次,那这个方法的复杂度就是多少。

显然,是 O ( l o g 2 b ) O(log_2b) O(log2b)的复杂度。

我们的 b = 998244351 b=998244351 b=998244351时, l o g 2 b ≈ 30 log_2b\approx30 log2b30

不用快速幂算法,计算机就要可怜的计算 998244351 998244351 998244351次了。

不过,快速幂除了这种递归算法,还有另一种循环求解的方法。

我们直接不断平方,求出所有 a 2 n ( 2 n < p ) a^{2^n}(2^na2n(2n<p),显然,复杂度 O ( l o g 2 p ) O(log_2p) O(log2p)

然后,我们把 b b b拆成 2 2 2的幂的和:
a b = a 2 b 1 + 2 b 2 + ⋯ + 2 b k a^b=a^{2^{b_1}+2^{b_2}+\cdots+2^{b_k}} ab=a2b1+2b2++2bk
= a 2 b 1 × a 2 b 2 × ⋯ × a 2 b k =a^{2^{b_1}}\times a^{2^{b_2}}\times\cdots\times a^{2^{b_k}} =a2b1×a2b2××a2bk
显然 k ≤ l o g 2 p k\le log_2p klog2p,那么我们在预处理好的 a 2 n a^{2^n} a2n中找到我们想要的加起来就可以了。

然而作为OIer,天天和二进制打交道的人,显然应该知道怎么用位运算来简化代码:

const int p=998244353;
int qpow(int base,int exp){
    int ret;
    for(ret=1;exp;base=(1ll*base*base)%p,exp>>=1){
        if(exp&1)ret=(1ll*ret*base)%p;
    }
    return ret;//这代码保熟。
}

鉴于计算机的递归需要开栈赋值等操作,大部分情况是慢于同复杂度的循坏操作,所以建议采用循环实现代码。


这是本博客的第500行,请自动忽略这一行


那么,我们就又优化了预处理组合数的复杂度。

O ( n l o g p ) 代码 O(nlogp)代码 O(nlogp)代码

const int MAXN=1e7+5,p=998244353;
int qpow(int x,int b){
    int ret;
    for(ret=1;b;x=(1ll*x*x)%p,b>>=1){
        if(b&1)ret=(1ll*ret*base)%p;
    }
    return ret;
}
int fac[MAXN],inv[MAXN];
void init(){
    fac[0]=1;//此处定义0的阶乘为1
    for(int i=1;i<MAXN;i++){
        fac[i]=fac[i-1]*i%p;
        inv[i]=pow(fac[i],p-2);
    }
}
int C(int n,int m){
    return fac[n]*inv[m]%p*inv[n-m]%p;
}

O ( n l o g p ) O(nlogp) O(nlogp)的复杂度已经很优秀了,但是在这道题 1 0 7 10^7 107的大数据下,我们希望能找到复杂度更优的方法,也就是 O ( n ) O(n) O(n)的线性预处理。

思考阶乘的逆元是个什么东西。

n ! n! n! a n a_n an,则其逆元为 a n − 1 a_n^{-1} an1
a n − 1 = ( n ! ) − 1 = 1 n ! = 1 1 × 1 2 × ⋯ 1 n = 1 − 1 × 2 − 1 × ⋯ × n − 1 a_n^{-1}=(n!)^{-1}=\frac{1}{n!}=\frac 11\times\frac 12\times \cdots \frac 1n=1^{-1}\times 2^{-1}\times \cdots \times n^{-1} an1=(n!)1=n!1=11×21×n1=11×21××n1

差点就写一个 ( n ! ) − 1 = ( n − 1 ) ! (n!)^{-1}=(n^{-1})! (n!)1=(n1)!了。

不过意思是对的,阶乘的逆元就是逆元的某种意义的阶乘。

那么我们就像阶乘一样预处理呗, i n v i = i n v i − 1 × a i − 1 inv_i=inv_{i-1}\times a_i^{-1} invi=invi1×ai1

然而。

你不还要算 a i − 1 a_i^{-1} ai1???

显然这样没有没有去掉求逆元的复杂度。

——但是,你学过移项啊!

i n v i × a i = i n v i − 1 × a i − 1 × a i inv_i\times a_i=inv_{i-1}\times a_i^{-1}\times a_i invi×ai=invi1×ai1×ai
i n v i × a i = i n v i − 1 inv_i\times a_i=inv_{i-1} invi×ai=invi1
i n v i − 1 = i n v i × a i inv_{i-1}=inv_i\times a_i invi1=invi×ai

我们可以从后往前算啊,先用快速幂求出最后一个逆元,再一个个往前递推,这样做法就是线性的了。

O ( n ) O(n) O(n) code:

const int MAXN=1e7+10;
int fac[MAXN],inv[MAXN];
const int mod=998244353;
int qpow(int x,int b){
    int ret=1;
    while(b){  
        if(b&1)ret=ret*x%mod;
        x=x*x%mod;
        b>>=1;
    }
    return ret;
}
void init(int n){//预处理前n个数
    fac[0]=1;
    for(int i=1;i<=n;i++){
        fac[i]=fac[i-1]*i%mod;
    }
    inv[n]=qpow(fac[n],mod-2);
    for(int i=n;i;i--){
        inv[i-1]=inv[i]*i%mod;
    }
}
int c(int n,int m){
    if (n<m||m<0) return 0;//不可能的情况,当然只有0种方案。
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}

正题

现在,你知道一个有理数可以在模意义下用一个整数表示,并完全支持加减乘操作,而有理数概率计算只需要用到加法原理和乘法原理,所以完全够用。并且你还知道了怎么 O ( n ) O(n) O(n)预处理组合数,来加速你的代码。

我们现在可以潜心看题了。

动点 P P P 位于数轴的 x = 0 x=0 x=0 处。规定数轴右端为正方向。

每个时刻,动点 P P P 会选择随机向左或向右移动一个单位长度。形式化地,设动点 P P P 目前在 x = p x=p x=p,动点 P P P 会随机移动到 x = p − 1 x=p−1 x=p1 x = p + 1 x=p+1 x=p+1。其中,向左移动的概率为 a a + b \frac {a}{a+b} a+ba​,向右移动的概率为 b a + b \frac {b}{a+b} a+bb​。

已知动点 P P P 移动了 m m m 次,求动点 P P P 到达过 x = n x=n x=n 的概率。

答案对 998244353 998244353 998244353 取模。

题解

首先我们需要一个网格图的题意转换:

#N0258. 诚【NOIP2023模拟赛T3】_第1张图片

读完了么?

这个直接除下来得到的概率肯定是 a = b a=b a=b的时候。

我们对网格图进行拆边,向左的边拆成 a a a条,向右的边拆成 b b b条,这样,就像中考题一样,都成了等概率事件,直接计数即可。

我们现在可以颓颓柿子:

P 总 = ∑ ( l , r ) ∈ { ( l , r ) ∣ r − l = n } l + r ≤ m ( a l b r − 1 C l + ( r − 1 ) l × b − a l − 1 b r C ( l − 1 ) + r l − 1 × a ( a + b ) l + r ) P_总=\sum_{(l,r)\in\{(l,r)|r-l=n\}}^{l+r\le m} \left ( \frac{ a^lb^{r-1}C_{l+(r-1)}^{l}\times b- a^{l-1}b^{r}C_{(l-1)+r}^{l-1}\times a }{(a+b)^{l+r}}\right ) P=(l,r){(l,r)rl=n}l+rm((a+b)l+ralbr1Cl+(r1)l×bal1brC(l1)+rl1×a)
= ∑ ( l , r ) ∈ { ( l , r ) ∣ r − l = n } l + r ≤ m a l b r ( a + b ) l + r ( C l + r − 1 l − C l − 1 + r l − 1 ) =\sum_{(l,r)\in\{(l,r)|r-l=n\}}^{l+r\le m} \frac{ a^lb^r }{(a+b)^{l+r}} \left ( C_{l+r-1}^{l}- C_{l-1+r}^{l-1} \right ) =(l,r){(l,r)rl=n}l+rm(a+b)l+ralbr(Cl+r1lCl1+rl1)
= ∑ ( l , r ) ∈ { ( l , r ) ∣ r − l = n } l + r ≤ m ( a a + b ) l ( b a + b ) r ( C l + r − 1 l − C l − 1 + r l − 1 ) =\sum_{(l,r)\in\{(l,r)|r-l=n\}}^{l+r\le m} \left ( \frac{a}{a+b}\right )^l \left ( \frac{b}{a+b}\right )^r \left ( C_{l+r-1}^{l}-C_{l-1+r}^{l-1}\right ) =(l,r){(l,r)rl=n}l+rm(a+ba)l(a+bb)r(Cl+r1lCl1+rl1)

既然 r = l + n r=l+n r=l+n

= ∑ l = 0 l + l + n ≤ m ( a a + b ) l ( b a + b ) l + n ( C l + l + n − 1 l − C l − 1 + l + n l − 1 ) =\sum_{l=0}^{l+l+n\le m} \left ( \frac{a}{a+b}\right )^l \left ( \frac{b}{a+b}\right )^{l+n} \left ( C_{l+l+n-1}^{l}-C_{l-1+l+n}^{l-1}\right ) =l=0l+l+nm(a+ba)l(a+bb)l+n(Cl+l+n1lCl1+l+nl1)
显然两个乘方每次计算都是可以用上一次计算的结果,后面组合数也预处理好了可以 O ( 1 ) O(1) O(1)查询,整个和式 O ( n ) O(n) O(n)枚举就好了。

代码就出来了:

#include
#define int long long
using namespace std;
const int MAXN=1e7+10;
int fac[MAXN],inv[MAXN];
const int mod=998244353;
int qpow(int x,int b){
    int ret=1;
    while(b){  
        if(b&1)ret=ret*x%mod;
        x=x*x%mod;
        b>>=1;
    }
    return ret;
}
void init(int n){
    fac[0]=1;
    for(int i=1;i<=n;i++){
        fac[i]=fac[i-1]*i%mod;
    }
    inv[n]=qpow(fac[n],mod-2);
    for(int i=n;i;i--){
        inv[i-1]=inv[i]*i%mod;
    }
}
int c(int n,int m){
    if (n<m||m<0) return 0;
    return fac[n]*inv[m]%mod*inv[n-m]%mod;
}
int n,m,a,b;
signed main(){
    cin>>n>>m>>a>>b;
    init(1e7);
    int ans=0;
    int pa=1,pb=1;
    int invapb=qpow(a+b,mod-2);
    a=a*invapb%mod;
    b=b*invapb%mod;
    pb=qpow(b,n);
    for(int l=0;l+l+n<=m;l++){
        int r=l+n;
        ans=(ans+pa*pb%mod*(c(l+r-1,l)+mod-c(l+r-1,l-1))%mod)%mod;
        pa=pa*a%mod,pb=pb*b%mod;
    }
    cout<<ans;
    return 0;
}

那,就,完了。

我知道,文章有点虎头蛇尾,但是后面的我已经尽能力写得更详细了。

return 0;

你可能感兴趣的:(概率论)