[ZJOI2019]开关(生成函数+背包DP)

注:以下p[i]均表示概率

设F(x)为按i次开关后到达终止状态方案数的EGF,显然F(x)=π(ep[i]x/p+(-1)s[i]e-p[i]x/p)/2,然而方案包含一些多次到达合法方案的状态,需将其排除。n次操作回到原状态的方案数的生成函数G(x)=π(ep[i]x/p+e-p[i]x/p)/2。实现时只需要记录F(x)=Σa[i]eix/P中a[i](-P<=i<=P)的系数即可(G(x)也一样),于是暴力复杂度O(nP)。H(x)为答案的生成函数,显然F、G、H所对应的OGF f、g、h满足f(x)=g(x)h(x)。然后就是EGF向OGF的转化:F(x)=Σa[i]eix/P→f(x)=Σa[i]/(1-ix/P),其中-P<=i<=P,于是此时要求h'(1),然后根据除法求导公式,可以计算出h'(x),但x=1时函数不收敛。然后推一下式子就发现本题其实是背包DP了(这里打数学公式太累了就省略一些内容了),复杂度O(nP)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+7,mid=1e5,mod=998244353;
int n,sp,ans,s[N],a[N],f[N],g[N],tmp[N];
int qpow(int a,int b)
{
    int ret=1;
    while(b)
    {
        if(b&1)ret=1ll*ret*a%mod;
        a=1ll*a*a%mod,b>>=1;
    }
    return ret;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&s[i]);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    f[mid]=g[mid]=1;
    for(int i=1;i<=n;i++)
    {
        sp+=a[i];
        for(int j=-sp;j<=sp;j++)tmp[j+mid]=(g[j-a[i]+mid]+g[j+a[i]+mid])%mod;
        memcpy(g,tmp,sizeof g);
        for(int j=-sp;j<=sp;j++)tmp[j+mid]=(f[j-a[i]+mid]+(s[i]?mod-f[j+a[i]+mid]:f[j+a[i]+mid]))%mod;
        memcpy(f,tmp,sizeof f);
    }
    for(int i=-sp;i<sp;i++)ans=(ans+1ll*(g[i+mid]-f[i+mid]+mod)*qpow(sp-i,mod-2))%mod;
    ans=1ll*ans*sp%mod;
    printf("%d",ans);
}
View Code

猜你喜欢

转载自www.cnblogs.com/hfctf0210/p/10836881.html