【洛谷5326】[ZJOI2019] 开关(指数型生成函数)

点此看题面

  • (n)个开关,每个开关有一个权值(p_i)
  • 每轮操作会以(frac{p_i}{sum_{i=1}^{n}p_i})的概率选中一个开关改变它的状态。
  • 给定目标状态(s_i),问达到该状态的期望轮数。
  • (nle100,sum p_ile5 imes10^4)

发现生成函数什么的果然还是啥也不会。。。

看来与去年相比,在这方面上唯一的进步就是能看懂题解了。。。

指数型生成函数

首先,方便起见令(P=sum_{i=1}^np_i)

考虑对于一个目标状态(s_i=0)的位置,相当于要选中这个开关偶数次,而对于(s_i=1)的位置就是要选中奇数次。

也就是说,第(i)个位置的指数型生成函数就是:

[F_i(x)= egin{cases} frac{1}{0!}+frac{(frac{p_i}{P})^2x^2}{2!}+frac{(frac{p_i}{P})^4x^4}{4!}+...=frac{e^{frac{p_i}{P}x}+e^{-frac{p_i}{P}x}}2(s_i=0)\ frac{frac{p_i}{P}x}{1!}+frac{(frac{p_i}{P})^3x^3}{3!}+frac{(frac{p_i}{P})^5x^5}{5!}+...=frac{e^{frac{p_i}{P}x}-e^{-frac{p_i}{P}x}}2(s_i=1) end{cases} =frac{e^{frac{p_i}{P}x}+(-1)^{s_i}e^{-frac{p_i}{P}x}}2 ]

那么总生成函数应该就是(F_1(x)*F_2(x)*F_3(x)*...*F_n(x)),也就是:

[F(x)=prod_{i=1}^nfrac{e^{frac{p_i}{P}x}+(-1)^{s_i}e^{-frac{p_i}{P}x}}2 ]

考虑这个函数的实际意义,它对应的普通生成函数(i)次项系数表示的是(i)轮操作之后能得到目标状态的概率。

但我们要求的是它第一次到达目标状态的期望轮数,而如果仅仅通过(F(x))这一个函数计算答案,到达一次目标状态之后我们依然会统计到它的贡献!

因此我们还要构造一个函数(G(x)),让它的(i)次项系数表示(i)轮操作之后所有开关状态不变的概率。把这和(F(x))一对比,发现(G(x))其实就是(s_i)全等于(0)(F(x)),二者的求法应该是完全一致的(具体的求法之后会介绍)。

那么,我们最终的生成函数就应该是(H(x)=frac{F(x)}{G(x)})

然后我们先考虑最终答案的表示,应该是:

[sum_{i=0}^{+infty}i imes [x^i]H(x) ]

这形式看起来非常眼熟,仔细一想发现实际上这就是(H'(1))

再次提醒,最终的(F(x),G(x),H(x))都应该是普通生成函数

背包求解生成函数系数

现在,我们来考虑如何求出(F(x))的系数。

显然这玩意儿是一个无限函数,不可能说求出它每一项的系数。

故考虑先把(F(x))表示成(sum_ia_ie^{frac{i}{P}x})的形式。观察之前的式子容易发现,最终的(i)取值范围应该是([-P,P])

既然(n,P)的范围都比较小,我们可以直接(O(nP))暴力背包来求出(a_i)这个数组。

众所周知(e^{frac ipx})的第(k)次项系数为((frac ip)^k)

因为我们最终需要的是普通生成函数,所以把(F(x))转化成(OGF)得到:

[F(x)=sum_{i=-P}^Pa_ie^{frac iPx}=sum_{i=-P}^Pfrac{a_i}{1-frac iPx} ]

同理我们求出(G(x))的数组(b_i),得到:

[G(x)=sum_{i=-P}^Pfrac{b_i}{1-frac iPx} ]

最终的答案计算

考虑(H'=(frac FG)'),而根据除法求导法则有((frac FG)'=frac{F'G-G'F}{G^2}),因此:

[H'(1)=frac{F'(1)G(1)-G'(1)F(1)}{G^2(1)} ]

但是,我们发现(F,F',G,G')四个函数在(x=1)的时候都不收敛,没办法直接求出(F(1),F'(1),G(1),G'(1))

不过,由于我们要求的是((frac FG)'),如果给(F)(G)都乘上同一个多项式(1-x),式子的结果并不会改变。

而这样一来,就会发现我们要求的东西都变得非常简单了,实际上此时就有:(这应该非常显然)

[F(1)=a_P,F'(1)=sum_{i=-P}^{P-1}frac{a_i}{frac iP-1}\ G(1)=b_P,G'(1)=sum_{i=-P}^{P-1}frac{b_i}{frac iP-1} ]

那么答案就是:

[H'(1)=frac{(sum_{i=-P}^{P-1}frac{a_i}{frac iP-1})b_P-(sum_{i=-P}^{P-1}frac{b_i}{frac iP-1})a_P}{b_P^2} ]

化简一下得到:

[H'(1)=frac{1}{b_P^2}sum_{i=-P}^{P-1}frac{a_ib_P-b_ia_P}{frac iP-1} ]

具体实现还是非常简单的。

代码:(O(nsum p))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define SZ 50000
#define X 998244353
#define I2 499122177
using namespace std;
int n,s[N+5],P,p[N+5],a[2*SZ+5],b[2*SZ+5];
I int QP(RI x,RI y) {RI t=1;W(y) y&1&&(t=1LL*t*x%X),x=1LL*x*x%X,y>>=1;return t;}
int tmp[2*SZ+5];I void DP(int* f)//背包求解多项式系数
{
	f[P]=1;for(RI i=1,j;i<=n;++i)//枚举每个位置
	{
		for(j=0;j<=2*P;++j) tmp[j]=f[j],f[j]=0;//备份当前数组
		for(j=0;j+p[i]<=(P<<1);++j) f[j+p[i]]=(1LL*I2*tmp[j]+f[j+p[i]])%X;//第一种转移
		for(j=p[i];j<=(P<<1);++j) f[j-p[i]]=(1LL*(s[i]?X-1:1)*I2%X*tmp[j]+f[j-p[i]])%X;//第二种转移
	}
}
int main()
{
	RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",s+i);for(i=1;i<=n;++i) scanf("%d",p+i),P+=p[i];
	RI t=0,ipt=QP(P,X-2);for(DP(a),memset(s,0,sizeof(s)),DP(b),i=-P;i^P;++i)//两次背包求出两个多项式系数
		t=((1LL*a[i+P]*b[P<<1]-1LL*a[P<<1]*b[i+P]%X+X)%X*QP((1LL*(i+X)*ipt-1)%X,X-2)+t)%X;//统计答案
	return printf("%d
",1LL*QP(b[P<<1],X-3)*t%X),0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5326.html