玩游戏,洛谷P4705,NTT+生成函数+可怕的公式推导+可怕的代码

本文探讨了一个游戏算法问题,通过使用二项式定理和生成函数,将复杂的问题转化为卷积形式,实现了算法的高效求解。文章详细介绍了如何利用生成函数和快速傅立叶变换(FFT)进行数组卷积,最终解决了原问题。

正题

      这个游戏整整玩了我三天。

      题意就是要你求ans(k)=\sum_{i=1}^n\sum_{j=1}^m\frac{(a_i+b_j)^k}{nm}。还要你求ans(1)~ans(t)。那我们暂且先不理会nm这玩意儿。

      利用二项式定理,化简一下可以得到:

      ans(k)=\sum_{i=1}^n\sum_{j=1}^m(a_i+b_j)^k \\=\sum_{i=1}^n\sum_{j=1}^m\sum_{z=0}^{k}C_k^z a_i^z*b_j^{k-z} \\=\sum_{z=0}^kC_k^z\sum_{i=1}^na_i^z\sum_{j=1}^mb_j^{k-z} \\=\sum_{z=0}^k\frac{k!}{z!(k-z)!}\sum_{i=1}^na_i^z\sum_{j=1}^mb_j^{k-z} \\=k!\sum_{z=0}^k\frac{\sum_{i=1}^na_i^z}{z!}*\frac{\sum_{j=1}^mb_j^{k-z}}{(k-z)!}

      看到了喜闻乐见的卷积形式,但是我们难以求出可用于卷积的两个数组A(x)=\sum_{i=1}^na_i^x,B(x)=\sum_{i=1}^nb_J^x

      这个其实可以用生成函数来解决,在我的博客中就以这个模型来介绍生成函数,大家可以看看。

      所以我们最后再用这两个函数做一遍卷积,乘上k!后除以nm输出即可。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;

const int maxm=1e5*6+10;
int n,m,t;
long long A[maxm],B[maxm];
long long f[maxm],g[maxm],p[maxm],q[maxm];
long long inv[maxm],fac[maxm];
int limit,l,where[maxm];
const long long mod=998244353;

long long ksm(long long x,long long t){
    long long tot=1;
    while(t){
        if(t&1) (tot*=x)%=mod;
        (x*=x)%=mod;
        t/=2;
    }
    return tot;
}

void ntt(long long *now,int idft){
    for(int i=0;i<limit;i++) if(i<where[i]) swap(now[i],now[where[i]]);
    long long wn,w,a,b;
    for(int l=2;l<=limit;l*=2){
        wn=ksm(3,(mod-1)/l);
        if(idft==-1) wn=ksm(wn,mod-2);
        for(int i=0;i<limit;i+=l){
            w=1;
            for(int x=i,y=i+l/2;y<i+l;x++,y++,(w*=wn)%=mod){
                a=now[x],b=now[y]*w%mod;
                now[x]=(a+b)%mod;
                now[y]=(a+mod-b)%mod;
            }
        }
    }
}

void multi(long long *now,int a,int b,int c,int d){
    limit=1,l=0;
    while(limit<d-a) limit*=2,l++;
    for(int i=0;i<limit;i++) where[i]=((where[i>>1]>>1)|((i&1)<<(l-1)));
    for(int i=0;i<=b-a;i++) f[i]=now[a+i];
    for(int i=0;i<=d-c;i++) g[i]=now[c+i];
    ntt(f,1);ntt(g,1);
    for(int i=0;i<limit;i++) (f[i]*=g[i])%=mod;ntt(f,-1);
    long long inv=ksm(limit,mod-2);
    for(int i=0;i<=d-a;i++) now[i+a]=f[i]*inv%mod;
    for(int i=0;i<limit;i++) f[i]=g[i]=0;
}

void dfs(long long *now,int l,int r){
    if(l==r) return ;
    int mid=(l+r)/2;
    dfs(now,l,mid);
    dfs(now,mid+1,r);
    multi(now,l*2,mid*2+1,(mid+1)*2,r*2+1);
}

void get_inv(long long *inv,long long *now,int n){
    if(n==1) {inv[0]=ksm(now[0],mod-2);return ;}
    get_inv(inv,now,(n+1)/2);
    for(int i=0;i<n;i++) p[i]=inv[i]*2%mod;
    limit=1,l=0;
    while(limit<3*n-2) limit*=2,l++;
    for(int i=0;i<limit;i++) where[i]=((where[i>>1]>>1)|((i&1)<<(l-1))),q[i]=0;
    for(int i=0;i<n;i++) q[i]=now[i];
    for(int i=n;i<limit;i++) q[i]=0;
    ntt(inv,1);ntt(q,1);
    for(int i=0;i<limit;i++) inv[i]=inv[i]*inv[i]%mod*q[i]%mod;ntt(inv,-1);
    long long op=ksm(limit,mod-2);
    for(int i=0;i<n;i++) inv[i]=(p[i]+mod-inv[i]*op%mod)%mod;
    for(int i=n;i<limit;i++) inv[i]=0;
}

void mul(long long *a,long long *b,int l1,int l2){
    limit=1,l=0;
    while(limit<l1+l2-1) limit*=2,l++;
    for(int i=l1;i<limit;i++) a[i]=0;
	for(int i=l2;i<limit;i++) b[i]=0;
    for(int i=0;i<limit;i++) where[i]=((where[i>>1]>>1)|((i&1)<<(l-1)));
    ntt(a,1);ntt(b,1);
    for(int i=0;i<limit;i++) (a[i]*=b[i])%=mod;ntt(a,-1);
    long long inv=ksm(limit,mod-2);
    for(int i=0;i<limit;i++) (a[i]*=inv)%=mod;
}

int op;

void print(){
	for(int i=0;i<=op;i++) printf("%lld ",A[i]);printf("\n");
	for(int i=0;i<=op;i++) printf("%lld ",B[i]);printf("\n"); 
}

int main(){
    scanf("%d %d",&n,&m);
    for(int i=0;i<n;i++) scanf("%lld",&A[i*2+1]),A[i*2]=1,A[i*2+1]=(mod-A[i*2+1])%mod;
    for(int i=0;i<m;i++) scanf("%lld",&B[i*2+1]),B[i*2]=1,B[i*2+1]=(mod-B[i*2+1])%mod;
    scanf("%d",&t);op=max(n,max(m,t));
	dfs(A,0,n-1);dfs(B,0,m-1);
	fac[0]=1;for(int i=1;i<=op;i++) fac[i]=(fac[i-1]*i)%mod;
    inv[op]=ksm(fac[op],mod-2);for(int i=op-1;i>=0;i--) inv[i]=(inv[i+1]*(i+1))%mod;
    get_inv(f,A,op+1);get_inv(g,B,op+1);
    for(int i=0;i<=n;i++) A[i]=A[i+1]*(i+1)%mod;
    for(int i=0;i<=m;i++) B[i]=B[i+1]*(i+1)%mod;
    mul(A,f,n,op+1);mul(B,g,m,op+1);
    for(int i=op;i>=1;i--) A[i]=(mod-A[i-1])%mod*inv[i]%mod;A[0]=n;
    for(int i=op;i>=1;i--) B[i]=(mod-B[i-1])%mod*inv[i]%mod;B[0]=m;
    mul(A,B,op+1,op+1);
    long long temp=ksm((long long)n*m%mod,mod-2);
    for(int i=1;i<=t;i++) printf("%lld\n",A[i]*fac[i]%mod*temp%mod);
}

 

### NTT算法代码实现 对于NTT(数论变换),其实现通常依赖于选择合适的原根作为单位根的替代品。下面是一个基于C++的简单NTT实现示例,该例子展示了如何利用NTT来进行两个多项式的快速卷积: ```cpp #include <iostream> #include <vector> using namespace std; typedef long long ll; const int MOD = 998244353; // 模数, 需要是质数且有对应的原根g const int G = 3; // 原根 // 快速幂取模 ll qpow(ll a, ll b) { ll res = 1; while(b){ if(b & 1) res = res * a % MOD; a = a * a % MOD; b >>= 1; } return res; } void ntt(vector<ll> &a, bool inv) { int n = a.size(); for(int i=0,j=0;i<n;++i){ if(i>j) swap(a[i],a[j]); for(int l=n>>1;(j^=l)<l;l>>=1); } for(int m=2;m<=n;m<<=1){ ll wm = qpow(G,(MOD-1)/m); // 计算当前层旋转因子 if(inv) wm=qpow(wm,MOD-2); // 如果是逆变换,则求wm的逆元 for(int j=0;j<n;j+=m){ ll w = 1; for(int k=j;k<j+m/2;++k){ ll t=w*a[k+m/2]%MOD; a[k+m/2]=(a[k]-t+MOD)%MOD; a[k]=(a[k]+t)%MOD; w=w*wm%MOD; } } } if(inv){ // 对结果除以n ll rev = qpow(n, MOD - 2); for(auto& x : a)x=x*rev%MOD; } } ``` 此段代码实现了基本的NTT及其反向操作。注意这里选择了`MOD=998244353`以及相应的原根`G=3`,这是因为这个特定组合允许高效的计算,并能保证良好的数值稳定性[^1]。 当需要处理更大范围的数据或是不同的应用场景时,可能还需要调整这些参数的选择。此外,在实际应用中,可能会遇到更复杂的场景,比如多维NTT或者是针对不同类型的输入数据优化过的版本。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值