[BZOJ4555][Tjoi2016&Heoi2016]求和(NTT)

题目:

我是超链接

题解:

我的Ma呀这种东西应该给递推式吗?为什么不给我通项,于是我扪心自问不是TJ和HE选手,上网查了通项

S(n,m)=1m!k=0m(1)kCkm(mk)n S ( n , m ) = 1 m ! ∑ k = 0 m ( − 1 ) k C m k ( m − k ) n

好叭我就把ta代入到这个凶残的柿子里叭
这里写图片描述
这个第二项运行到i我就很不服了,改成n是不是好看一些?
随便改的基础是第二类斯特林数的情景:将n个不同的球放入m个无差别的盒子中,要求盒子非空的方案数,当n < m的时候,方案数自然是0呀
那么我们把改良后的柿子拿出来:
f(n)=i=0nj=0nk=1j(1)kCkj(jk)i2j f ( n ) = ∑ i = 0 n ∑ j = 0 n ∑ k = 1 j ( − 1 ) k C j k ( j − k ) i ∗ 2 j

然后怎么办呢?似乎看起来有什么 (1)k ( − 1 ) k (jk)i ( j − k ) i 让我们能够联想到卷积之类的东西,但那个组合数好像很碍事,那我们把它化成阶乘的形式。
f(n)=i=0nj=0nk=1j(1)kj!k!(jk)!(jk)i2j f ( n ) = ∑ i = 0 n ∑ j = 0 n ∑ k = 1 j ( − 1 ) k j ! k ! ( j − k ) ! ( j − k ) i ∗ 2 j

把与k和j-k无关的丢到外面去
f(n)=j=0n2jj!k=1j(1)kk!ni=0(jk)i(jk)! f ( n ) = ∑ j = 0 n 2 j ∗ j ! ∑ k = 1 j ( − 1 ) k k ! ∑ i = 0 n ( j − k ) i ( j − k ) !

好叭这样后面的就可以卷积了,即 f(i)=(1)ii! f ( i ) = ( − 1 ) i i ! g(i)=nj=0iji! g ( i ) = ∑ j = 0 n i j i !
那么这个柿子的最终形态就是:
f(n)=j=0n2jj!kf(j)g(jk) f ( n ) = ∑ j = 0 n 2 j ∗ j ! ∗ ∑ k f ( j ) ∗ g ( j − k )

因为有一个模数,我们用NTT

代码:

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
const int N=300005;
const int mod=998244353;
LL a[N],b[N],ans,mi[N],mul[N],inv[N],invmul[N];int r[N],n,fn;
LL ksm(LL a,LL k)
{
    LL ans=1;
    for (;k;k>>=1,a=a*a%mod)
      if (k&1) ans=ans*a%mod;
    return ans;
}
void NTT(LL *a,int id)
{
    for (int i=0;i<n;i++)
      if (i<r[i]) swap(a[i],a[r[i]]);
    for (int k=1;k<n;k<<=1)
    {
        LL wn=ksm(3,(mod-1)/(k<<1));
        for (int i=0;i<n;i+=(k<<1))
        {
            LL w=1;
            for (int j=0;j<k;j++,w=w*wn%mod)
            {
                LL x=a[i+j],y=w*a[i+j+k]%mod;
                a[i+j]=(x+y)%mod; a[i+j+k]=(x-y+mod)%mod;
            }
        }
    }
    if (id==-1) reverse(a+1,a+n);
}
void init()
{
    mi[0]=1; mul[0]=1;
    for (int i=1;i<=n;i++) mi[i]=mi[i-1]*2ll%mod,mul[i]=mul[i-1]*(LL)i%mod;
    inv[1]=1;
    for (int i=2;i<=n;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    invmul[0]=1;
    for (int i=1;i<=n;i++) invmul[i]=invmul[i-1]*inv[i]%mod;
    a[0]=b[0]=1;
    for (int i=1;i<=fn;i++) 
    {
        a[i]=invmul[i];
        if (i&1) a[i]=-a[i];
        if (i==1) b[i]=fn+1;
        else b[i]=invmul[i]*inv[i-1]%mod*(ksm(i,fn+1)-1)%mod;
    }
}
int main()
{
    scanf("%d",&fn);int L=0;
    for (n=1;n<=fn*2;n<<=1) L++;
    for (int i=0;i<n;i++) 
      r[i]=(r[i>>1]>>1) | ((i&1)<<L-1);
    init();
    NTT(a,1); NTT(b,1);
    for (int i=0;i<n;i++) a[i]=a[i]*b[i]%mod;
    NTT(a,-1);
    for (int i=0;i<=fn;i++) 
    {
        LL t=mi[i]*mul[i]%mod*a[i]%mod*inv[n]%mod;//别忘了/n
        ans=(ans+t)%mod;
    }
    printf("%lld",(ans+mod)%mod);
}

普及向:

NTT是什么呀
这里写图片描述
这里写图片描述
这里写图片描述
这里给出几组常用模数吧
p c k g
104857601 25 22 3
998244353 119 23 3
1004535809 479 21 3
事实上会出现的只有g,这里的g是模数p的原根,求原根的话,枚举2~p,如果到p-1之前ksm(i,p-x)都不是1,说明i是原根

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值