[JZOJ5740]幻想世界 快速数论变换

这篇博客探讨了在解决[JZOJ5740]幻想世界问题时,如何利用快速数论变换来处理α,β的贡献以及ak,bk的贡献。通过将DP状态视为二维坐标,描述了从左边或下面进行状态转移的过程。文章详细解释了如何枚举两个点(x1,y1)和(x2,y2),并计算(x1,y1)到(x2,y2)的路径对答案的影响。同时,文章还介绍了如何分别计算ak和bkbk的贡献,提供了相应的代码实现。" 112889586,8342891,使用WPF创建自动递增编号,"['WPF开发', '数据库操作', '编程实践', '前端开发', '数据处理']
摘要由CSDN通过智能技术生成

首先发现 α,β α , β 的贡献和 ak,bk a k , b k 的贡献是可以分开计算的,把一个DP状态看成是二维坐标上的一个点,转移就是每次从左边 (x1,y) ( x − 1 , y ) 或者从下面 (x,y1) ( x , y − 1 ) 转移。
先考虑计算 α,β α , β 的贡献。
考虑枚举两个点 (x1,y1),(x2,y2)(x1x2,y1y2) ( x 1 , y 1 ) , ( x 2 , y 2 ) ( x 1 ≤ x 2 , y 1 ≤ y 2 ) ,计算转移到 (x1,y1) ( x 1 , y 1 ) 的那一步对 (x2,y2) ( x 2 , y 2 ) 的贡献,从两点之间的路径条数入手可以得到:

(pα+qβ)x2=1ny2=1nhx2(n+1)+y2x1=1x2y1=1y2(x2x1+y2y1x2x1)px2x1qy2y1 ( p α + q β ) ∑ x 2 = 1 n ∑ y 2 = 1 n h x 2 ( n + 1 ) + y 2 ∑ x 1 = 1 x 2 ∑ y 1 = 1 y 2 ( x 2 − x 1 + y 2 − y 1 x 2 − x 1 ) p x 2 − x 1 q y 2 − y 1

这样显然无法计算,转换成枚举 i=x2x1,j=y2y1 i = x 2 − x 1 , j = y 2 − y 1
(pα+qβ)i=0n1j=0n1(i+ji)piqjx=i+1ny=j+1nhx(n+1)+y ( p α + q β ) ∑ i = 0 n − 1 ∑ j = 0 n − 1 ( i + j i ) p i q j ∑ x = i + 1 n ∑ y = j + 1 n h x ( n + 1 ) + y

拆开组合数,
(pα+qβ)i=0n1j=0n1(i+j)!pii!qjj!x=i+1nhx(n+1)y=j+1nhy ( p α + q β ) ∑ i = 0 n − 1 ∑ j = 0 n − 1 ( i + j ) ! p i i ! q j j ! ∑ x = i + 1 n h x ( n + 1 ) ∑ y = j + 1 n h y

先把最后的 h h 用等比数列求和求出来,然后换成枚举s=i+j,然后就可以卷积了,
(pα+qβ)k=0n2k!i+j=kpii!h(i+1)(n+1)(h(n+1)(ni)1)hn+11×qjj!hj+1(hnj1)h1 ( p α + q β ) ∑ k = 0 n − 2 k ! ∑ i + j = k p i i ! h ( i + 1 ) ( n + 1 ) ( h ( n + 1 ) ( n − i ) − 1 ) h n + 1 − 1 × q j j ! h j + 1 ( h n − j − 1 ) h − 1

再考虑计算 ak a k 的贡献, bk b k 同理。
对于每一个 ak a k ,可以分别计算贡献求和:

k=1nakx=kny=1n(xk+y1xk)pxkqyhx(n+1)+y ∑ k = 1 n a k ∑ x = k n ∑ y = 1 n ( x − k + y − 1 x − k ) p x − k q y h x ( n + 1 ) + y

组合数里 y1 y − 1 是因为最后一步必须选择纵坐标 1 − 1 ,但这样还是无法计算,因为 ak a k n n 个,于是还是考虑枚举差i=xk
i=0n1y=1n(i+y1i)piqyx=i+1naxihx(n+1)+y ∑ i = 0 n − 1 ∑ y = 1 n ( i + y − 1 i ) p i q y ∑ x = i + 1 n a x − i h x ( n + 1 ) + y

i=0n1y=1n(i+y1)!piaii!qyhy(y1)! ∑ i = 0 n − 1 ∑ y = 1 n ( i + y − 1 ) ! p i a i ′ i ! q y h y ( y − 1 ) !

其中 ai=nx=i+1axihx(n+1) a i ′ = ∑ x = i + 1 n a x − i h x ( n + 1 ) ,可以先卷积预处理出来。然后像之前一样枚举 i+y i + y 再卷起来就行了。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 524290
#define ll long long
#define up(x,y) (x=(x+(y))%mod)
using namespace std;
const int mod=998244353;
const int g=3;
ll ksm(ll a,ll b){ll r=1;for(b=(b+mod-1)%(mod-1);b;b>>=1){if(b&1)r=r*a%mod;a=a*a%mod;}return r;}
ll inv(ll x){return ksm(x,mod-2);}
int n,m,r[N];
ll h,hn,A,B,P,Q,a[N],b[N],ans,fac[N],ifac[N],p[N],q[N],pow_P[N],pow_Q[N],pow_h[N],pow_hn[N],c[N];
void ntt(ll a[],int m,int dft)
{
    for(int i=0;i<m;i++)
        r[i]=(r[i>>1]>>1)|((i&1)*(m>>1));
    for(int i=0;i<m;i++)
        if(i<r[i]) swap(a[i],a[r[i]]);
    for(int i=1;i<m;i<<=1)
    {
        ll wn=ksm(g,(mod-1)/(i<<1)*dft);
        for(int j=0;j<m;j+=(i<<1))
        {
            ll wk=1;
            for(int k=j;k<j+i;k++)
            {
                ll x=a[k],y=wk*a[k+i]%mod;
                a[k]=(x+y)%mod;a[k+i]=(x-y+mod)%mod;
                wk=wk*wn%mod;
            }
        }
    }
    if(dft==-1) for(int i=0,t=inv(m);i<m;i++) a[i]=a[i]*t%mod;
}
void multi(ll a[],ll b[],ll c[])
{
    ntt(a,m,1);ntt(b,m,1);
    for(int i=0;i<m;i++)
        c[i]=a[i]*b[i]%mod;
    ntt(c,m,-1);    
}
int main()
{
    scanf("%d%lld%lld%lld",&n,&h,&A,&B);

    ll pa,pb,qa,qb;
    scanf("%lld%lld%lld%lld",&pa,&pb,&qa,&qb);
    P=pa*inv(pb)%mod;Q=qa*inv(qb)%mod;
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    for(int i=1;i<=n;i++)
        scanf("%lld",&b[i]);
    for(m=1;m<((n+1)<<1);m<<=1);
    hn=ksm(h,n+1);
    fac[0]=1;
    for(int i=1;i<=m;i++)
        fac[i]=fac[i-1]*i%mod;
    ifac[m]=inv(fac[m]);
    for(int i=m-1;i>=0;i--)
        ifac[i]=ifac[i+1]*(i+1)%mod;
    pow_P[0]=pow_Q[0]=pow_h[0]=pow_hn[0]=1;
    for(int i=1;i<=n;i++)
    {
        pow_P[i]=pow_P[i-1]*P%mod;
        pow_Q[i]=pow_Q[i-1]*Q%mod;
        pow_h[i]=pow_h[i-1]*h%mod;
        pow_hn[i]=pow_hn[i-1]*hn%mod;
    }
    for(int i=1;i<=n;i++)
        up(ans,pow_hn[i]*a[i]+pow_h[i]*b[i]);       
    //cal A B
    ll ih=inv(h-1),ihn=inv(hn-1);
    for(int i=0;i<n;i++)
    {
        p[i]=pow_P[i]*pow_hn[i+1]%mod*(pow_hn[n-i]-1)%mod*ifac[i]%mod*ihn%mod;
        q[i]=pow_Q[i]*pow_h[i+1]%mod*(pow_h[n-i]-1)%mod*ifac[i]%mod*ih%mod;
    }
    multi(p,q,c);
    for(int i=0;i<(n<<1);i++)
        up(ans,(P*A+Q*B)%mod*fac[i]%mod*c[i]);

    //cal a
    for(int i=0;i<=(n>>1);i++)
        swap(a[i],a[n-i]);
    memset(p,0,sizeof(p));
    for(int i=1;i<=n;i++)
        p[i]=pow_hn[i];
    multi(a,p,a);
    memset(p,0,sizeof(p));
    memset(q,0,sizeof(q));  
    for(int i=0;i<n;i++)    
        p[i]=pow_P[i]*ifac[i]%mod*a[i+n]%mod;
    for(int i=1;i<=n;i++)
        q[i]=pow_Q[i]*pow_h[i]%mod*ifac[i-1]%mod;
    multi(p,q,c);
    for(int i=1;i<(n<<1);i++)
        up(ans,fac[i-1]*c[i]%mod);  


    //cal b 
    for(int i=0;i<=(n>>1);i++)
        swap(b[i],b[n-i]);
    memset(p,0,sizeof(p));
    for(int i=1;i<=n;i++)
        p[i]=pow_h[i];
    multi(b,p,b);

    memset(p,0,sizeof(p));
    memset(q,0,sizeof(q));
    for(int i=1;i<=n;i++)   
        p[i]=pow_P[i]*pow_hn[i]%mod*ifac[i-1]%mod;
    for(int i=0;i<n;i++)
        q[i]=pow_Q[i]*ifac[i]%mod*b[i+n]%mod;

    multi(p,q,c);

    for(int i=1;i<(n<<1);i++)
        up(ans,fac[i-1]*c[i]%mod);


    printf("%lld",ans); 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值