ARC136F - PGF 学习笔记初步

· · 题解

Description

给定一个 h\times w01 矩阵和非负整数序列 \{b_n\},接下来每秒会在 h\times w 个格子中均匀随机地选取一个将其取反。

问期望第几秒会第一次满足:对于任意的 i 有第 i 行恰有 b_i1

## Solution 闲话: 我终于会 PGF 了!!!! 这真的只是铜牌题吗???我只能说,too hard for me。 由于笔者是 PGF 初学者,所以本篇文章**会讲得非常详细**以来加深笔者自己地理解和帮助其他初学者。 下面进入正文: ### 前置知识: 1. 基本组合数学知识(如二项式定理等)。 2. 基本微积分知识(如求导,泰勒展开等)。 阅读此文你**并不需要有关于概率生成函数(PGF)的知识**,因为本文可能会从头到尾地介绍一遍。 你甚至可能**不需要普通生成函数(OGF)和指数生成函数(EGF)的知识**(虽然理论上是这样,但是我觉得想要做这题还是得了解生成函数)。 ### 符号约定: - $F(x)$ 表示序列 $f$ 的普通生成函数(OGF):$F(x)=\sum\limits_{i\ge 0} f_ix^i$。 - $\hat{F}(x)$ 表示序列 $f$ 的指数生成函数(EGF):$\hat{F}(x)=\sum\limits_{i\ge 0} \frac{f_i}{i!}x^i=\sum\limits_{i\ge 0} \frac{[x^i]F(x)}{i!}x^i$。 - $[x^i]F(x)$ 表示多项式 $F(x)$ 其第 $i$ 次项的系数。 - $F*G$ 表示的是两个多项式卷积得到的多项式,具体的,若 $H=F*G$,则我们有: - $[x^n]H(x)=\sum\limits_{m=0}^n [x^m]F(x)\times [x^{n-m}]G(x)$。 - 这个东西也可以写作 $H(x)=F(x)G(x)$。 - $F'(x)$ 表示的是对多项式 $F(x)$ 求导后得到的多项式。 - $\Pr(A)$ 表示的是 $A$ 事件成立的概率。 - $\operatorname{E}(X)$ 表示的是随机变量 $X$ 的期望取值。 ### Pre - 概率生成函数(PGF) 如果我们现在有一个**取值是非负整数**的随机变量 $X$,则我们记其概率生成函数(PGF)为: $$ F_X(x)=\sum\limits_{i\ge 0} \Pr(X=i)x^i $$ 下面简记为 $F(x)$。 或者从另一个角度理解:即序列 $\{\Pr(X=1),\Pr(X=2),\cdots\}$ 的普通生成函数(OGF)。 更一般的,我们的 PGF 可能是关于**一系列事件**而非一个随机变量的(特别的,我们可以将 $X=1,2,\cdots$ 看作是**一系列不同的事件**)。 在这题中,我们的随机变量 $X$ 也就是**停时时间**(即第一次符合条件的时间)。 则我们的答案(**期望停时时间**)就是: $$ \operatorname{E}(X)=\sum\limits_{i\ge 0} \Pr(X=i)\cdot i $$ 我们发现:如果对 $F(x)$ 求导,我们就可以得到: $$ F'(x)=\sum\limits_{i\ge 0} \Pr(X=i)\cdot i\cdot x^{i-1} $$ 如果我们此时带入 $x=1$,就可以惊讶地发现: $$ \begin{aligned} F'(1) & = \sum\limits_{i\ge 0} \Pr(X=i)\cdot i\cdot 1^{i-1}\\ & =\sum\limits_{i\ge 0} \Pr(X=i)\cdot i\\ & =\operatorname{E}(X) \end{aligned} $$ 也就是说,$F'(1)$ 就是我们想要的答案! ### Pre - 解决「第一次」问题 我们令 $F(x)$ 为答案的 PGF。 我们注意到本题中想要的是「**第一次**符合条件的时间」,这是很烦的,我们考虑这样子处理: 我们记 $S,T$ 分别为题目中的初始和最终局面,事件 $A(U\to V,i)$ 表示的是从初始是 $U$ 局面经过**恰好** $i$ 时刻变成了 $V$ 局面,则我们列出关于 $A(S\to T,*)$ 的 PGF $G(x)$ 和关于 $A(T\to T,*)$ 的 PGF $H(x)$: $$ G(x)=\sum\limits_{i\ge 0} \Pr(A(S\to T,i))x^i\\ H(x)=\sum\limits_{i\ge 0} \Pr(A(T\to T,i))x^i $$ 则我们有:$G=F*H$。 - 证明:我们可以认为是,当我们在 $i$ 时刻**第一次**从初始状态到达了最终状态,然后**又经过**了 $j$ 时刻的一系列操作**从最终状态又回到了最终状态**;又因为这两个事件是独立的,则我们有 $[x^i]F(x)\times [x^j]H(x)\to [x^{i+j}]G(x)$,即 $G=F*H$。 注意到我们关心的是 $F'(1)$ 的值,则根据 $G=F*H$,我们有 $F=\frac{G}{H}$,根据商的求导法则,我们有: $$ F'(1)=\frac{G'(1)H(1)-H'(1)G(1)}{H^2(1)} $$ 则我们现在将问题转换为了求 $G(1),G'(1),H(1),H'(1)$ 的值,而这样子就消除了「第一次」的限制。 下面我们默认只讨论 $G(1),G'(1)$ 的求解,对于 $H(1),H'(1)$ 则是几乎一模一样的,在实现上只需要改一下进行 dp 时传进去的参数即可。 ### Part 1 - 一个 dp 注意到我们一个格子**翻了奇数次相当于翻了一次,翻了偶数次则相当于没翻**。 我们不妨假定:每个格子**最多只会被翻转一次**。考虑计算从 $S\to T$ 的方案总数。 注意到我们只关心每一行 $1$ 的个数,所以考虑按行 dp。 记录 $f_{i,j}$ 表示考虑前 $i$ 行翻转恰好 $j$ 次使得满足条件的的方案总数。 我们记第 $i$ 行在初始状态有 $a_i$ 个 $1$,而最终状态就是题目给定的有 $b_i$ 个 $1$。 则考虑枚举将多少个 $1$ change 成了 $0$。 如果有 $k$ 个 $0\to 1$,则我们应有 $b_i-(a_i-k)$ 个 $1\to 0$,则得到转移方程: $$ f_{i-1,j}\binom{a_i}{k}\binom{w-a_i}{b_i-(a_i-k)}\to f_{i,j+k+(b_i-(a_i-k))} $$ 记 $n=h\times w$ 为格子总数量,则这个 dp 是 $\mathcal{O}(n^2)$ 的,可以接受。 我们设 $\{c_n\}$ 为最终的答案,即 $c_i=f_{h,i}$。 ### Part 2 - 构造生成函数 现在我们对于每一个格子给出其被翻转偶数 or 奇数次的 PGF: $$ P_0(x)=\sum\limits_{i\equiv0\pmod{2}} \frac{x^i}{n^i}\\ P_1(x)=\sum\limits_{i\equiv1\pmod{2}} \frac{x^i}{n^i} $$ 这时候我们看起来只需要枚举实际上有多少个格子被翻转(即被翻转奇数次),然后将对应的生成函数卷起来即可! 但是实际上并不是,因为**不同格子之间的翻转是区分顺序的**,所以我们要使用 EGF 进行区分(这是因为两个 EGF 的卷积表述成最终的序列是会乘上一个组合数的系数),即得到: $$ \hat{G}(x)=\sum\limits_{i\ge 0} c_i\hat{P_1}^i(x)\hat{P_0}^{n-i}(x) $$ 尝试推导 $\hat{P_0}$ 和 $\hat{P_1}$。 我们给出: $$ P(x)=\sum\limits_{i\ge 0} \frac{x^i}{n^i}\\ Q(x)=\sum\limits_{i\ge 0} (-1)^i\frac{x^i}{n^i}=\sum\limits_{i\ge 0} \frac{x^i}{(-n)^i} $$ 可以得到: $$ \begin{aligned} \hat{P}(x) & = \sum\limits_{i\ge 0} \frac{x^i}{i!n^i}\\ & = \sum\limits_{i\ge 0} \frac{(\frac{x}{n})^i}{i!}\\ & = e^{\frac{x}{n}} \end{aligned} $$ 最后一步是考虑 $e^x$ 的泰勒展开。 同理我们可以得到的是:$\hat{Q}(x)=e^{-\frac{x}{n}}$。 我们注意到有: $$ P_0(x)=\frac{P(x)+Q(x)}{2} $$ 则可以得到的是: $$ \hat{P_0}(x)=\frac{\hat{P}(x)+\hat{Q}(x)}{2} $$ 也就是对等式两边同时取 EGF,因为我们注意到 $[x^n]$ 的相对关系没有发生变化,所以等号仍然成立。 即 $$ \hat{P_0}(x)=\frac{e^{\frac{x}{n}}+e^{-\frac{x}{n}}}{2} $$ 同理有: $$ \hat{P_1}(x)=\frac{e^{\frac{x}{n}}-e^{-\frac{x}{n}}}{2} $$ 则我们得到了: $$ \hat{G}(x)=\sum\limits_{i\ge 0} c_i\left(\frac{e^{\frac{x}{n}}-e^{-\frac{x}{n}}}{2}\right)^i\left(\frac{e^{\frac{x}{n}}+e^{-\frac{x}{n}}}{2}\right)^{n-i} $$ ### Part 2 - 一个柿子的推 考虑化简上面 EGF: $$ \begin{aligned} \hat{G}(x) & = \sum\limits_{i\ge 0} c_i\left(\frac{e^{\frac{x}{n}}-e^{-\frac{x}{n}}}{2}\right)^i\left(\frac{e^{\frac{x}{n}}+e^{-\frac{x}{n}}}{2}\right)^{n-i}\\ & = \frac{1}{2^n}\sum\limits_{i=1}^n c_i\left(e^{\frac{x}{n}}-e^{-\frac{x}{n}}\right)^i\left(e^{\frac{x}{n}}+e^{-\frac{x}{n}}\right)^{n-i}\\ & = \frac{1}{2^n}\sum\limits_{i=1}^n c_i\sum\limits_{j=0}^i \binom{i}{j}(e^{\frac{x}{n}})^j(-e^{-\frac{x}{n}})^{i-j}\sum\limits_{k=0}^{n-i} \binom{n-i}{k}(e^{\frac{x}{n}})^k(e^{-\frac{x}{n}})^{n-i-k}\\ & = \frac{1}{2^n}\sum\limits_{i=1}^n c_i\sum\limits_{j=0}^i \sum\limits_{k=0}^{n-i}\binom{i}{j}\binom{n-i}{k}(e^{\frac{x}{n}})^{j+k}(e^{-\frac{x}{n}})^{n-(j+k)}(-1)^{i-j}\\ & = \frac{1}{2^n}\sum\limits_{i=1}^n c_i\sum\limits_{j=0}^i \sum\limits_{k=0}^{n-i}\binom{i}{j}\binom{n-i}{k}(e^{\frac{x}{n}})^{2(j+k)-n}(-1)^{i-j}\\ & = \frac{1}{2^n}\sum\limits_{k=0}^{n}e^{(\frac{2k-n}{n})x}\sum\limits_{i=1}^n c_i\sum\limits_{j=0}^i \binom{i}{j}\binom{n-i}{k-j}(-1)^{i-j}\\ & = \frac{1}{2^n}\sum\limits_{k=0}^{n}e^{(\frac{2k-n}{n})x}\sum\limits_{i=1}^n c_i[x^k](x-1)^{i}(x+1)^{n-i}\\ & = \frac{1}{2^n}\sum\limits_{k=0}^{n}e^{(\frac{2k-n}{n})x}[x^k]\sum\limits_{i=1}^n c_i(x-1)^{i}(x+1)^{n-i}\\ \end{aligned} $$ 其中第三个等号处用二项式定理将其暴力展开,倒数第三个等号处改为枚举 $j+k$,倒数第二个等号处将 $\sum\limits_{j=0}^i \binom{i}{j}\binom{n-i}{k-j}(-1)^{i-j}$ 改为其 OGF 写法(考虑二项式定理将其展开即可获证)。 ### Part 3 - 计算答案 我们先暴力计算出 $\sum\limits_{i=1}^n c_i(x-1)^{i}(x+1)^{n-i}$ 的多项式 $C(x)$。 则柿子化为 $$ \hat{G}(x)=\frac{1}{2^n}\sum\limits_{k=0}^{n}e^{(\frac{2k-n}{n})x}[x^k]C(x) $$ 这时候我们去掉 EGF,还原回 OGF: $$ G(x)=\frac{1}{2^n}\sum\limits_{k=0}^{n}\frac{[x^k]C(x)}{1-(\frac{2k-n}{n})x} $$ 这一部分即为先将 $e^{(\frac{2k-n}{n})x}$ 泰勒展开得到: $$ \frac{1}{0!}+\frac{x^{\frac{2k-n}{n}}}{1!}+\frac{(x^{\frac{2k-n}{n}})^2}{2!}+\frac{(x^{\frac{2k-n}{n}})^3}{3!}+\cdots $$ 由于 EGF->OGF 会将 $[x^i]$ 去 $\times i!$,所以其 OGF 为: $$ 1+x^{\frac{2k-n}{n}}+(x^{\frac{2k-n}{n}})^2+(x^{\frac{2k-n}{n}})^3+\cdots $$ 然后再化为封闭形式: $$ \frac{1}{1-(\frac{2k-n}{n})x} $$ 此时我们代入 $x=1$ 即可得到 $G(1)$ 的值,然而有一个问题是当 $k=n$ 时我们会得到 $\frac{1}{0}$,这无法处理。 我们考虑令 $A(x)=(1-x)G(x),B(x)=(1-x)H(x)$,此时我们仍有 $F=\frac{A}{B}$,所以不影响答案。 而这时当 $k=n$ 时我们外层的 $1-x$ 和内层的 $1-x$ 相互抵消,所以对 $A(1)$ 恰好贡献为 $1\times [x^n]C(x)$。 而对于 $k\ne n$ 时,我们记 $t=\frac{2k-n}{n},v=[x^k]C(x)$,则所得多项式为 $\frac{v(x-1)}{1-tx}$。 - 这时 $x-1=0$,不对 $A(1)$ 产生贡献。 - 此时导函数为 $\frac{(t-1)v}{(1-tx)^2}$,对 $A'(1)$ 贡献为 $\frac{v}{t-1}$。 注意别忘记除掉 $2^n$。 同理我们可以计算出 $B(1)$ 和 $B'(1)$ 的值,则可以得到: $$ F'(1)=\frac{A'(1)B(1)-B'(1)A(1)}{B^2(1)} $$ 这就是答案。 暴力实现复杂度是 $\mathcal{O}(n^2)$ 的,已经可以通过;使用一些多项式知识可以做到 $\mathcal{O}(n\operatorname{polylog}(n))$。 下面给出 $\mathcal{O}(n^2)$ 的实现。 ## Code ```cpp #include<bits/stdc++.h> //#pragma GCC optimize(3,"Ofast","inline") //#define int long long #define i128 __int128 #define ll long long #define ull unsigned long long #define uint unsigned int #define ld double #define PII pair<int,int> #define INF 0x3f3f3f3f #define INFLL 0x3f3f3f3f3f3f3f3f #define chkmax(a,b) a=max(a,b) #define chkmin(a,b) a=min(a,b) #define rep(k,l,r) for(int k=l;k<=r;++k) #define per(k,r,l) for(int k=r;k>=l;--k) #define cl(f,x) memset(f,x,sizeof(f)) #define pcnt(x) __builtin_popcount(x) #define lg(x) (31-__builtin_clz(x)) using namespace std; void file_IO() { // system("fc .out .ans"); freopen(".in","r",stdin); freopen(".out","w",stdout); } bool M1; template<int p> struct mint { int x; mint() { x=0; } mint(int _x) { x=_x; } friend mint operator + (mint a,mint b) { return a.x+b.x>=p? a.x+b.x-p:a.x+b.x; } friend mint operator - (mint a,mint b) { return a.x<b.x? a.x-b.x+p:a.x-b.x; } friend mint operator * (mint a,mint b) { return 1ll*a.x*b.x%p; } friend mint operator ^ (mint a,ll b) { mint res=1,base=a; while(b) { if(b&1) res*=base; base*=base; b>>=1; } return res; } friend mint operator ~ (mint a) { return a^(p-2); } friend mint operator / (mint a,mint b) { return a*(~b); } friend mint & operator += (mint& a,mint b) { return a=a+b; } friend mint & operator -= (mint& a,mint b) { return a=a-b; } friend mint & operator *= (mint& a,mint b) { return a=a*b; } friend mint & operator /= (mint& a,mint b) { return a=a/b; } friend mint operator ++ (mint& a) { return a+=1; } friend mint operator -- (mint& a) { return a-=1; } }; const int MOD=998244353; #define mint mint<MOD> const int N=5e3+5; mint jc[N],inv_jc[N]; void init(int n=5000) { jc[0]=1; rep(i,1,n) jc[i]=jc[i-1]*i; inv_jc[n]=~jc[n]; per(i,n-1,0) inv_jc[i]=inv_jc[i+1]*(i+1); } mint C(int n,int m) { if(m<0||n<m) return 0; return jc[n]*inv_jc[n-m]*inv_jc[m]; } int n,h,w; mint tmp[N][N],t[N]; void calc(int a[],int b[],mint c[]) { rep(i,0,h) { rep(j,0,n) tmp[i][j]=0; } tmp[0][0]=1; rep(i,1,h) { rep(j,0,w) t[j]=0; rep(j,0,a[i]) { int d=b[i]-a[i]+j; if(j+d>=0) t[j+d]+=C(a[i],j)*C(w-a[i],d); } rep(j,0,w) { rep(k,0,n-j) tmp[i][j+k]+=tmp[i-1][k]*t[j]; } } rep(i,0,n) c[i]=tmp[h][i]; } mint p[N]; void calc(mint c[],mint f[]) { rep(i,0,n) p[i]=C(n,i); rep(i,0,n) { if(i) { rep(j,1,n) p[j]-=p[j-1]; per(j,n,1) p[j]=p[j-1]-p[j]; p[0]=MOD-p[0]; } rep(j,0,n) f[j]+=p[j]*c[i]; } } int a[N],b[N]; char s[N]; mint c1[N],c2[N],f[N],g[N]; void solve() { scanf("%d%d",&h,&w); rep(i,1,h) { scanf("%s",s+1); int cnt=0; rep(j,1,w) cnt+=s[j]=='1'; a[i]=cnt; } rep(i,1,h) scanf("%d",&b[i]); n=h*w; calc(a,b,c1); calc(c1,f); calc(b,b,c2); calc(c2,g); mint sf=0,sg=0,sdf=0,sdg=0; rep(i,0,n-1) { mint val=(mint(2*i)-mint(n))/mint(n); sdf+=f[i]/(val-mint(1)); sdg+=g[i]/(val-mint(1)); } sf+=f[n]; sg+=g[n]; mint inv=(~mint(2))^n; sf*=inv; sg*=inv; sdf*=inv; sdg*=inv; printf("%d\n",((sdf*sg-sf*sdg)/(sg*sg)).x); } bool M2; signed main() { //file_IO(); int testcase=1; init(); //scanf("%d",&testcase); while(testcase--) solve(); fprintf(stderr,"used time = %ldms\n",1000*clock()/CLOCKS_PER_SEC); fprintf(stderr,"used memory = %lldMB\n",(&M2-&M1)/1024/1024); return 0; } ```