玲珑杯 1160 - 康娜与玲珑杯

题意,在n本书中要拿k本书的倍数的方案,每本书都不同,一本都不拿也算一种方案
1k3e41k3e521n1018

开始以为是直接求C(n,0)+C(n,k)+C(n,2k)…

求不出来 orz

看了题解后 问了yql大佬

先是可以得到一个递推式
F[i][j]:表示前i本书,拿j本的方案
F[i][j]=F[i-1][j]+F[i-1][j-1]

因为j比较大,我们可以用滚动数组,j=j% k
然后可以得到

这里写图片描述

答案就是f[n][0],我们只用求第一行就行了。
设,A为图中第一个矩阵,A矩阵是k*k,如果朴素求第一行的话,时间复杂度为k*k*logn,超时gg… 然后我们发现这个可以用NTT来加速
注意到A是循环矩阵
(什么是循环矩阵?类似于 a1a3a2a2a1a3a3a2a1 的矩阵)

如果A= a1a3a2a2a1a3a3a2a1
则A*A的第一行为(a1*a1+a2*a3+a3*a2 , a1*a2+a2*a1+a3*a3, a1*a3+a2*a2+a3*a1)
这个就是 f(x)=a1+(a2)x+(a3)x2 的卷积模3为0,1,2的值
卷积就可以用NTT了~用NTT的总时间复杂度为O(k*logn*logk),当k为3e4时,为1e7,但k为3e5时就会超时。因为当k>3e4时,k只能为2的幂。一般去长度为k的循环卷积,肯定做的是>2k的FFT,来保证不会出错,但是如果k是2的次幂,就可以直接做长度为k的FFT,就可以直接变成点值之后快速幂。(yql教的:>)当k为2的幂次,时间大概是O(k*logn)

<从这个题中学到了很多,感谢yql~>
<基本上是yql的代码….>

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;
const int maxn = 600005;
#define mod  998244353
#define ll long long
int A[maxn],B[maxn],Ans[maxn],X[maxn];
ll n;int k;
int gg=3;

int fexp(int x,int p){int ans=1;for(;p;p>>=1,x=1LL*x*x%mod)if(p&1)ans=1LL*ans*x%mod;return ans;}

void NTT(int *a,int f,int k){
    for(int i=0,j=0;i<k;i++){
        if(i>j)swap(a[i],a[j]);
        for(int l=k>>1;(j^=l)<l;l>>=1);
    }

    for(int i=1;i<k;i<<=1)
    {
        int w=fexp(gg,(f*(mod-1)/(i<<1)+mod-1)%(mod-1));
        for(int j=0;j<k;j+=i<<1){int e=1;
            for(int k=0;k<i;k++,e=1LL*e*w%mod){int x,y;
                x=a[j+k];y=1LL*a[j+k+i]*e%mod;
                a[j+k]=(x+y)%mod;a[j+k+i]=(x-y+mod)%mod;
            }
        }
    }

    if(f==-1){
        int _inv=fexp(k,mod-2);
        for(int i=0;i<k;i++)a[i]=1LL*a[i]*_inv%mod;
    }
}
void Work(){
    if((k&(-k))==k)
    {
        NTT(X,1,k);
        NTT(Ans,1,k);
        for(;n;n>>=1)
        {
            if(n&1) for(int i=0;i<k;i++) Ans[i]=1LL*Ans[i]*X[i]%mod;
            for(int i=0;i<k;i++) X[i]=1LL*X[i]*X[i]%mod;
        }
        NTT(Ans,-1,k);
    }
    else {
        int t;
        for(t=1;t<=(k*2);t<<=1);
        for(;n;n>>=1)
        {
            if(n&1){
                for(int i=0;i<t;i++) A[i]=B[i]=0;
                for(int i=0;i<k;i++) A[i]=Ans[i],B[i]=X[i];
                NTT(A,1,t),NTT(B,1,t);
                for(int i=0;i<t;i++) A[i]=1LL*A[i]*B[i]%mod;
                NTT(A,-1,t);
                for(int i=0;i<t;i++) Ans[i]=0;
                for(int i=0;i<t;i++) Ans[i%k]=(Ans[i%k]+A[i])%mod;
            }
            for(int i=0;i<t;i++) A[i]=B[i]=0;
            for(int i=0;i<k;i++) A[i]=X[i];
            NTT(A,1,t);
            for(int i=0;i<t;i++) A[i]=1LL*A[i]*A[i]%mod;
            NTT(A,-1,t);
            for(int i=0;i<k;i++) X[i]=0;
            for(int i=0;i<t;i++) X[i%k]=(X[i%k]+A[i])%mod;
        }
    }
    printf("%d\n",Ans[0]);
}

void init()
{
    Ans[0]=1;X[0]++,X[k-1]++;
}

int main()
{
    scanf("%lld %d",&n,&k);
    init();
    Work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值