zoukankan      html  css  js  c++  java
  • 【洛谷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;
    }
    
  • 相关阅读:
    垂直的SeekBar:VerticalSeekBar
    android 获取路径目录方法以及判断目录是否存在,创建目录
    JSON
    列表和导航
    【前端积累】链接
    【前端积累】背景图像和背景替换
    【前段积累】可视化模型
    【前端积累】选择器
    银联支付-产品测试sdk使用流程
    【CSS系列】块级元素和行内元素
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5326.html
Copyright © 2011-2022 走看看