洛谷 P4389 付公主的背包(多项式exp)

题目链接
这题乍一眼应该是个生成函数题
我们可以把体积转化成生成函数 f ( x ) = [ x f(x)=[x f(x)=[x% v = = 0 ] v==0] v==0]
然后跑1e5次FFT就可以了
emmm,感觉有点不妙啊,1e5次FFT什么的好像肯定要T啊
我们感受下复杂度主要集中在卷积上面
因为求ln能化乘为加,所以可以对生成函数求一波ln康康
好的,发现很有规律 f ( x ) = [ x f(x)=[x f(x)=[x% v = = 0 ] ∗ 1 x v==0]*\frac{1}{x} v==0]x1
那么调和级数加一下,求exp就可以啦,至于相同项,肯定是出现几次加几倍喽
代码如下:

#include<bits/stdc++.h>
#define mod 998244353
#define gg 3
using namespace std;

int r[400040],n,v,cnt[200020];
long long inv[400040],tmp1[400040],tmp2[400040],tmp3[400040],ln[400040],f[400040],de[400040],in[400040],ex[400040]; 

long long kasumi(long long a,long long b)
{
    long long ans=1;
    while(b)
    {
        if(b&1) ans=ans*a%mod;
        a=a*a%mod;
        b>>=1; 
    }
    return ans;
}

void NTT(long long *a,int kd,int cnt)
{
    int lim=1<<cnt;
    for(int i=0;i<lim;i++)
    {
        if(i<r[i]) swap(a[i],a[r[i]]);
    }
    for(int mid=1;mid<lim;mid<<=1)
    {
        long long wn=kasumi(gg,(mod-1)/(mid<<1));
        if(kd) wn=kasumi(wn,mod-2);
        for(int i=0;i<lim;i+=(mid<<1))
        {
            long long w=1;
            for(int j=0;j<mid;j++,w=w*wn%mod)
            {
                long long x=a[i+j];
                long long y=a[i+j+mid]*w%mod;
                a[i+j]=(x+y)%mod;
                a[i+j+mid]=(x-y+mod)%mod;
            }
        }
    }
    if(kd)
    {
        long long invl=kasumi(lim,mod-2);
        for(int i=0;i<lim;i++)
        {
            a[i]=a[i]*invl%mod; 
        }
    }
}

void rev(int cnt)
{
    int lim=1<<cnt;
    for(int i=0;i<lim;i++)
    {
        r[i]=(r[i>>1]>>1)|((i&1)<<(cnt-1));
    }
}

void der(const long long *a,int cnt)
{
    int lim=1<<cnt;
    for(int i=0;i<lim;i++)
    {
        de[i]=a[i+1]*(i+1)%mod;
    } 
}

void inte(const long long *a,int cnt)
{
    int lim=1<<cnt;
    for(int i=lim-1;i>=1;i--)
    {
        in[i]=a[i-1]*kasumi(i,mod-2)%mod;
    }
}

void get_inv(const long long *a,int len)
{

    int cnt=0;
    for(int i=0;i<len;i++) tmp1[i]=tmp2[i]=inv[i]=0;
    inv[0]=kasumi(a[0],mod-2);
    int lim=1;
    while(lim<len)
    {
        cnt++;
        rev(cnt);
        lim<<=1;
        for(int i=0;i<lim>>1;i++)
        {
            tmp1[i]=inv[i];
            tmp2[i]=a[i];
        }
        NTT(tmp1,0,cnt);NTT(tmp2,0,cnt);
        for(int i=0;i<lim;i++)
        {
            tmp1[i]=(tmp1[i]*2ll%mod-tmp2[i]*tmp1[i]%mod*tmp1[i]%mod+mod)%mod;
        }
        NTT(tmp1,1,cnt);
        for(int i=0;i<lim>>1;i++)
        {
            inv[i]=tmp1[i];
        }
    }
}

void get_ln(const long long *a,int len)
{
    int lim=1,cnt=0;
    while(lim<len) lim<<=1,cnt++;
    get_inv(a,lim);
    der(a,cnt);
    NTT(inv,0,cnt); NTT(de,0,cnt);
    for(int i=0;i<lim;i++) ln[i]=inv[i]*de[i]%mod;
    NTT(ln,1,cnt);
    inte(ln,cnt);
}

void get_exp(const long long *a,int len)
{
    int cnt=1,lim=2;
    ex[0]=1;
    while(lim<len)
    {
        cnt++;
        lim<<=1;    
        get_ln(ex,lim);
        in[0]=0;
        in[0]=(in[0]-1+mod)%mod;
        for(int i=0;i<lim>>1;i++)
        {
            tmp1[i]=a[i];
            tmp2[i]=mod-in[i];
            tmp3[i]=ex[i];
        }
        for(int i=lim>>1;i<lim;i++) tmp1[i]=tmp2[i]=tmp3[i]=0;
        NTT(tmp3,0,cnt);NTT(tmp1,0,cnt);NTT(tmp2,0,cnt);
        for(int i=0;i<lim;i++)
        {   
            tmp3[i]=tmp3[i]*((tmp2[i]+tmp1[i])%mod)%mod;
        }
        NTT(tmp3,1,cnt);
        for(int i=0;i<lim>>1;i++)
        {
            ex[i]=tmp3[i];
        }
    }
}

int main()
{
    int tmp;
    scanf("%d%d",&n,&v);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&tmp);
        cnt[tmp]++;
    }
    for(int i=1;i<=v;i++)
    {
        if(cnt[i])
        {
            int tot=0;
            for(int j=i;j<=v;j+=i)
            {
                f[j]=(f[j]+kasumi(++tot,mod-2)*cnt[i]%mod)%mod;
            }
        }
    }
    // for(int i=1;i<=10;i++) printf("%lld ",f[i]);
    // puts("");
    get_exp(f,(v+1)*2);
    for(int i=1;i<=v;i++) printf("%lld\n",ex[i]);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值