P3803 【模板】多项式乘法(NTT)

这篇博客介绍了如何利用Number Theoretic Transform(NTT)算法来求解两个多项式的卷积问题,涉及到的概念包括FFT、快速幂以及原根。通过NTT算法,可以在O(n log n)的时间复杂度内完成卷积运算,适用于大整数模下的高效计算。博客还给出了998244353这个模数下的原根3和其逆元,并提供了完整的C++代码实现。
摘要由CSDN通过智能技术生成


原题链接

P3803
AC记录:Accepted

题目大意

给定一个 n n n 次多项式 F ( x ) F(x) F(x),和一个 m m m 次多项式 G ( x ) G(x) G(x)
请求出 F ( x ) F(x) F(x) G ( x ) G(x) G(x) 的卷积。

输入格式

第一行两个整数 n , m n,m n,m
接下来一行 n + 1 n+1 n+1 个数字,从低到高表示 F ( x ) F(x) F(x) 的系数。
接下来一行 m + 1 m+1 m+1 个数字,从低到高表示 G ( x ) G(x) G(x) 的系数。

输出格式

一行 n + m + 1 n+m+1 n+m+1 个数字,从低到高表示 F ( x ) ⋅ G ( x ) F(x) \cdot G(x) F(x)G(x) 的系数。

S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input

1 2
1 2
1 2 1

S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output

1 4 5 2

H i n t & E x p l a i n \mathbf{Hint\&Explain} Hint&Explain
样例如图所示。
其中粉色虚线为 F ( x ) F(x) F(x),绿色虚线为 G ( x ) G(x) G(x),蓝色实线为 F ( x ) ⋅ G ( x ) F(x)\cdot G(x) F(x)G(x)
HG6BvD.png

数据范围

保证输入中的系数大于等于 0 0 0 且小于等于 9 9 9
对于 100 % 100\% 100% 的数据, 1 ≤ n , m ≤ 10 6 1 \le n, m \leq {10}^6 1n,m106

解题思路

这篇博客会介绍一个和FFT差不多(?)的算法,NTT。

在介绍NTT之前,你需要知道一些前置的知识。


前置知识

1.FFT \texttt{1.FFT} 1.FFT
不会FFT的同学可以看我写的关于FFT的详解。
Click   me! \texttt{Click me!} Click me!

2. 快 速 幂 \texttt{2.}快速幂 2.
不会快速幂的同学可以看我写的关于快速幂的详解。
Click   me! \texttt{Click me!} Click me!

接下来就是重头戏NTT了。

NTT

其实就是FFT的加强版。


首先,我们要知道一个东西,叫做
a , p a,p a,p 互素,且 p > 1 p>1 p>1
对于满足不定方程 a x ≡ 1 ( m o d p ) a^x\equiv 1\pmod p ax1(modp) 最小的正整数解 n n n,我们称之为 a a a p p p 的阶,记作 δ p ( a ) \delta_p(a) δp(a)
然后是 原根
p p p 是正整数, a a a 是整数,若 δ p ( a ) δ_p(a) δp(a) 等于 ϕ ( p ) ϕ(p) ϕ(p),则称 a a a 为模 p p p 的一个原根。
例如:
∵ δ 7 ( 3 ) = 6 = ϕ ( 7 ) ∴ 3 为 7 的 一 个 原 根 。 \because \delta_7(3)=6=\phi(7)\\\therefore 3\sf为\rm7\sf的一个原根。 δ7(3)=6=ϕ(7)37


其实NTT和FFT的不同就是把FFT里面的单位根替换成了原根,而原根也满足我们用到的单位根的性质。
直接把所有的 ω n \omega_n ωn 替换成原根就得了。
但是原根如何计算呢?
只要记住一个结论:
ω n = g p − 1 n ( m o d p ) \large \omega_n=g^{\frac{p-1}n}\pmod p ωn=gnp1(modp)
这里给出 998244353 998244353 998244353 的原根是 3 3 3
3 3 3 对于 998244353 998244353 998244353 的逆元是 332748118 332748118 332748118

上代码

#include<bits/stdc++.h>

#define endl '\n'

using namespace std;

#define int ll

typedef long long ll;

namespace Polynomial{
    // The value of the variable below can be changed.
    const int   max_size_of_array=4000000;
    // The value of the variable above can be changed.
    const int   maxSize=max_size_of_array+10;
    const int   g=3,invg=332748118;
    const int   p=998244353;
    int         resort[maxSize];
    int         ta[maxSize];
    int         tb[maxSize];
    int         c[maxSize];
    int         d[maxSize];
    int         e[maxSize];
    int         h[maxSize];
    int         o[maxSize];
    int         q[maxSize];
    int         lim,dig;

    int power(int a,int b,int p)
    {
        int tar=1;
        for(; b; b>>=1,a=(1ll*a*a)%p)
            if(b&1)
                tar=(1ll*tar*a)%p;
        return tar%p;
    }

    void NTT(int *c,int lim,int state)
    {
        // cout<<"NTT"<<endl;
        for(int i=0; i<lim; i++)
            if(i<resort[i])
                swap(c[i],c[resort[i]]);
        for(int i=1; i<lim; i<<=1)
        {
            int W1n=power(g,1ll*(p-1)/(i<<1),p);
            for(int Size=i<<1,j=0; j<lim; j+=Size)
            {
                int W=1;
                for(int k=0; k<i; k++,W=(1ll*W*W1n)%p)
                {
                    int x=(1ll*c[j+k]+p)%p,y=(1ll*W*c[j+i+k]+p)%p;
                    c[j+k]=(1ll*x+y+p)%p;
                    c[j+i+k]=(1ll*x-y+p)%p;
                    // if(j+k==0)
                        // cout<<"\t"<<c[j+k]<<"\tNTT"<<endl;
                }
            }
        }
        if(state==1)
            return;
        int temp=power(lim,p-2,p);
        reverse(c+1,c+lim);
        for(int i=0; i<lim; i++)
            c[i]=1ll*c[i]*temp%p;
        return;
    }

    void getMul(int *f,int *g,int lenf,int leng)
    {
        memset(o,0,sizeof(o));
        memset(q,0,sizeof(q));
        lim=1,dig=0;
        while(lim<=lenf+leng)
            lim<<=1,dig++;
        for(int i=0; i<lim; i++)
            resort[i]=(resort[i>>1]>>1)|((i&1)<<(dig-1));
        NTT(f,lim,1);
        NTT(g,lim,1);
        for(int i=0; i<lim; i++)
            f[i]=((f[i]*g[i])%p+p)%p;
        NTT(f,lim,-1);
        return;
    }
}

using namespace Polynomial;

int         a[4000010];
int         b[4000010];
int         n,m;

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    /* Code */
    cin>>n>>m;
    for(int i=0; i<=n; i++)
        cin>>a[i];
    for(int i=0; i<=m; i++)
        cin>>b[i];
    getMul(a,b,n+1,m+1);
    for(int i=0; i<=n+m; i++)
        cout<<a[i]<<" ";
    return 0;
}

完美切题 ∼ \sim

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值