多项式——乘法逆元

多项式——乘法逆元

给定一个多项式 F ( x ) F(x) F(x) ,请求出一个多项式 G ( x ) G(x) G(x) 满足 F ( x ) G ( x ) ≡ 1 m o d    x n F(x)G(x) \equiv 1 \mod x^n F(x)G(x)1modxn 。系数对 998244353 998244353 998244353 取模。

P4238 【模板】多项式乘法逆

本文只介绍递推的方法,首先将 n n n 补齐到 2 的幂次,当 n = 1 n = 1 n=1 的时候,多项式中只有一项,那么 [ x 0 ] G ( x ) [x^0]G(x) [x0]G(x) 就是 [ x 0 ] F ( x ) [x^0]F(x) [x0]F(x) 的乘法逆元。

假设我们已知 F ( x ) H ( x ) ≡ 1 m o d    x n 2 F(x)H(x) \equiv 1 \mod x^{\frac{n}{2}} F(x)H(x)1modx2n ,那么显然 F ( x ) G ( x ) ≡ 1 m o d    x n 2 F(x) G(x) \equiv 1 \mod x^{\frac{n}{2}} F(x)G(x)1modx2n ,那么 F ( x ) [ G ( x ) − H ( x ) ] ≡ 0 m o d    x n 2 F(x)[G(x) - H(x)] \equiv 0 \mod x^{\frac{n}{2}} F(x)[G(x)H(x)]0modx2n,即 G ( x ) − H ( x ) ≡ 0 m o d    x n 2 G(x) - H(x) \equiv 0 \mod x^{\frac{n}{2}} G(x)H(x)0modx2n

两边同时平方得到 G ( x ) 2 + H ( x ) 2 − 2 G ( x ) H ( x ) ≡ 0 m o d    x n G(x)^2 + H(x)^2 -2G(x)H(x) \equiv 0 \mod x^n G(x)2+H(x)22G(x)H(x)0modxn ,这是因为原来是 x n 2 x^{\frac{n}{2}} x2n 的倍数,平方之后一定也是 x n x^n xn 的 倍数。

最后,两边同时乘以 F ( x ) F(x) F(x) 使得 G ( x ) 2 G(x)^2 G(x)2 项降幂,因为 F ( x ) G ( x ) ≡ 1 m o d    x n F(x)G(x) \equiv 1 \mod x^n F(x)G(x)1modxn 最后得到:

G ( x ) ≡ 2 H ( x ) − F ( x ) H ( x ) 2 m o d    x n G(x) \equiv 2H(x) - F(x)H(x)^2 \mod x^n G(x)2H(x)F(x)H(x)2modxn

用 NTT 来加速多项式乘法,时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn)

#include <bits/stdc++.h>

using namespace std;

using ll = long long;

#ifdef LLT_DBG
#define FR freopen("in.txt", "r", stdin)
#else
#define FR
#endif

template <ll P>
ll fpow(ll a, ll b)
{
    ll res = 1;
    for (; b; b >>= 1, a = (a * a) % P)
        if (b & 1)
            res = (res * a) % P;
    return res;
}

template <ll G, ll P>
struct NTT
{
    int _n;
    int E;
    vector<int> rev;

    /**
     * @brief 构建一个 NTT 计算器
     *
     * @param n 多项式最高项数
     */
    NTT(int n)
    {
        _n = 1;
        E = 0;
        while (_n < n)
        {
            _n <<= 1;
            E++;
        }
        rev.resize(_n);
        // 逆位置对换
        for (int i = 1; i < _n; i++)
        {
            rev[i] = (rev[i >> 1] >> 1) + ((i & 1) << (E - 1));
        }
    }

    void _rNTT(ll A[], ll k)
    {
        for (int i = 0; i < _n; i++)
            if (i < rev[i])
                swap(A[i], A[rev[i]]);

        for (int e = 1; e <= E; e++)
        {
            int m = 1 << e;

            for (int i = 0; i < _n; i += m)
            {
                int hf = m / 2;
                ll g = 1;
                ll gn = fpow<P>(fpow<P>(G, (P - 1) / m), k);

                for (int j = 0; j < hf; j++)
                {
                    ll x = A[i + j];
                    ll y = (A[i + j + hf] * g) % P;
                    A[i + j] = (x + y) % P;
                    A[i + j + hf] = (x - y) % P;
                    g = (g * gn) % P;
                }
            }
        }
    }

    /**
     * @brief NTT 过程
     *
     * @param A 系数数组
     */
    void doNTT(ll A[])
    {
        _rNTT(A, 1);
    }

    /**
     * @brief NTT 逆过程
     *
     * @param A 点值数组
     */
    void doINTT(ll A[])
    {
        ll ni = fpow<P>(_n, P - 2);
        _rNTT(A, P - 2);
        for (int i = 0; i < _n; i++)
            A[i] = (A[i] * ni) % P;
    }
};

ll A[500005];
ll F[500005];
ll B[500005];
ll G[500005];

const int mod = 998244353;

void solve()
{
    int n;
    cin >> n;

    for (int i = 0; i < n; i++)
    {
        cin >> A[i];
    }

    //  初始步骤
    G[0] = fpow<mod>(A[0], mod - 2);

    // 归纳步骤
    int i = 1;
    while (i < n)
    {
        i <<= 1;
        for (int j = 0; j < i; j++)
            B[j] = 0;

        // 注意最大界为 2i
        
        for (int j = 0; j < (i >> 1); j++)
            B[j] = G[j];

        for (int j = 0; j < i; j++)
            F[j] = A[j];

        NTT<3, mod> ntt(i << 1);
        ntt.doNTT(F);

        ntt.doNTT(B);

        for (int j = 0; j < (i << 1); j++)
            B[j] = B[j] * B[j] % mod * F[j] % mod;
        ntt.doINTT(B);

        for (int j = 0; j < i; j++)
        {
            G[j] = (2 * G[j] % mod - B[j]) % mod;
            G[j] = (G[j] + mod) % mod;
        }
    }

    for (int i = 0; i < n; i++)
    {
        G[i] = (G[i] + mod) % mod;
        cout << G[i] << " ";
    }
}

int main()
{
    FR;
    solve();
    return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值