【知识总结】多项式全家桶(一)(NTT、加减乘除和求逆)

我这种数学一窍不通的菜鸡终于开始学多项式全家桶了……

必须要会的前置技能:FFT(不会?戳我:【知识总结】快速傅里叶变换(FFT)

一、NTT

跟FFT功能差不多,只是把复数域变成了模域(计算复数系数多项式相乘变成计算在模意义下整数系数多项式相乘)。你看FFT里的单位圆是循环的,模一个质数也是循环的嘛qwq。\(n\)次单位根\(w_n\)怎么搞?看这里:【BZOJ3328】PYXFIB(数学)(内含相关证明。只看与原根和单位根相关的内容即可。)

注意裸的NTT要求模数\(p\)存在原根并且\(p-1\)是\(2\)的若干次幂的倍数(这个次数要大于多项式次数\(n\))。于是通常就会用著名的NTT模数:\(998244353=2^{23}\times 7\times 17+1\)。

节约篇幅,代码先不放了。后面所有代码里都有NTT模板……

二、多项式求逆

对于\(n\)次多项式\(A\),如果有多项式\(B\)满足\(AB\equiv 1 \mod x^{n+1}\),则称\(B\)是\(A\)在模\(x^{n+1}\)意义下的逆元(和整数逆元差不多)。通常采用倍增的方法求逆元。通常都会规定多项式系数在模\(p\)的意义下。

首先,\(A\)在模\(x\)的意义下就只有一个常数项,所以此时的逆元\(B\)也只有一个常数项,就是\(A\)的常数项模\(p\)的逆元。

如果我们知道\(B_0\)是\(A\)在模\(x^{\lceil\frac{n}{2}\rceil}\)意义下的逆元,现在要求\(B\)是\(A\)在模\(x^n\)意义下的逆元。根据题设,显然有:

\[AB=1\mod x^n\]

很明显,\(AB\)的\(1\)到\(n-1\)次项系数全是\(0\),所以模一个\(x\)的低于\(n\)次幂也一定是\(1\)。所以

\[AB_0=AB=1\mod x^{\lceil\frac{n}{2}\rceil}\]

那么

\[B-B_0=0\mod x^{\lceil\frac{n}{2}\rceil}\]

两边和模数同时平方:

\[B^2+B_0^2-2BB_0=0\mod x^n\]

两边同时乘\(A\),得到(别忘了\(AB=1\mod x^n\)):

\[B+AB_0^2-2B_0=0\mod x^n\]

然后移项,得到:

\[B=2B_0-AB_0^2\mod x^n\]

照着这个式子递归算就行了。

代码:

洛谷4238

注意代码里面的\(n\)是项数不是次数。一定要把没用的数组清空,以及进行NTT时把多项式项数写对。

代码最开始是防机惨护身符。

#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
#undef i
#undef j
#undef k
#undef max
#undef min
#undef swap
#undef sort
#undef true
#undef false
#undef if
#undef for
#undef while
#define _ 0
using namespace std;

namespace zyt
{
    template<typename T>
    inline bool read(T &x)
    {
        char c;
        bool f = false;
        x = 0;
        do
            c = getchar();
        while (c != EOF && c != '-' && !isdigit(c));
        if (c == EOF)
            return false;
        if (c == '-')
            f = true, c = getchar();
        do
            x = x * 10 + c - '0', c = getchar();
        while (isdigit(c));
        if (f)
            x = -x;
        return true;
    }
    template<typename T>
    inline void write(T x)
    {
        static char buf[20];
        char *pos = buf;
        if (x < 0)
            putchar('-'), x = -x;
        do
            *pos++ = x % 10 + '0';
        while (x /= 10);
        while (pos > buf)
            putchar(*--pos);
    }
    typedef long long ll;
    const int N = 1e5 + 10, B = 17, LEN = 1 << (B + 2) | 11, p = 998244353;
    inline int power(int a, int b)
    {
        a %= p, b %= p - 1;
        int ans = 1;
        while (b)
        {
            if (b & 1)
                ans = (ll)ans * a % p;
            a = (ll)a * a % p;
            b >>= 1;
        }
        return ans;
    }
    inline int get_inv(const int a)
    {
        return power(a, p - 2);
    }
    namespace Polynomial
    {
        int omega[LEN], winv[LEN], rev[LEN];
        namespace Primitive_Root
        {
            int cnt;
            pair<int, int> prime[20];
            inline void get_prime(int n)
            {
                cnt = 0;
                for (int i = 2; i * i <= n; i++)
                {
                    if (n % i == 0)
                        prime[cnt++] = make_pair(i, 0);
                    while (n % i == 0)
                        ++prime[cnt - 1].second, n /= i;
                }
                if (n > 1)
                    prime[cnt++] = make_pair(n, 1);
            }
            inline int get_g(const int n)
            {
                get_prime(n - 1);
                for (int i = 2; i < n; i++)
                {
                    bool flag = true;
                    for (int j = 0; j < cnt && flag; j++)
                        flag &= (power(i, (n - 1) / prime[j].first) != 1);
                    if (flag)
                        return i;
                }
                return -1;
            }
        }
        void ntt(int *a, const int *w, const int n)
        {
            for (int i = 0; i < n; i++)
                if (i < rev[i])
                    swap(a[i], a[rev[i]]);
            for (int l = 1; l < n; l <<= 1)
                for (int i = 0; i < n; i += (l << 1))
                    for (int k = 0; k < l; k++)
                    {
                        int tmp = (a[i + k] - (ll)w[n / (l << 1) * k] * a[i + l + k] % p + p) % p;
                        a[i + k] = (a[i + k] + (ll)w[n / (l << 1) * k] * a[i + l + k] % p) % p;
                        a[i + l + k] = tmp;
                    }
        }
        void init(const int n, const int lg2)
        {
            static int g = 0;
            if (!g)
                g = Primitive_Root::get_g(p);
            int w = power(g, (p - 1) / n), wi = get_inv(w);
            omega[0] = winv[0] = 1;
            for (int i = 1; i < n; i++)
            {
                omega[i] = (ll)omega[i - 1] * w % p;
                winv[i] = (ll)winv[i - 1] * wi % p;
            }
            for (int i = 0; i < n; i++)
                rev[i] = ((rev[i >> 1] >> 1) | ((i & 1) << (lg2 - 1)));
        }
        void inv(const int *a, int *ans, const int n)
        {
            if (n == 1)
                ans[0] = get_inv(a[0]);
            else
            {
                static int tmp[LEN];
                inv(a, ans, (n + 1) >> 1);
                int m = 1, lg2 = 0;
                while (m < (n << 1) - 1)
                    m <<= 1, ++lg2;
                memcpy(tmp, a, sizeof(int[n]));
                init(m, lg2);
                ntt(tmp, omega, m);
                ntt(ans, omega, m);
                for (int i = 0; i < m; i++)
                    ans[i] = (ans[i] * 2LL % p - (ll)tmp[i] * ans[i] % p * ans[i] % p + p) % p;
                ntt(ans, winv, m);
                int invm = get_inv(m);
                for (int i  = 0; i < m; i++)
                    ans[i] = (ll)ans[i] * invm % p;
                memset(ans + n, 0, sizeof(int[m - n]));
                memset(tmp, 0, sizeof(int[m]));
            }
        }
    }
    int a[LEN], b[LEN], n;
    int work()
    {
        read(n);
        for (int i = 0; i < n; i++)
            read(a[i]);
        Polynomial::inv(a, b, n);
        for (int i = 0; i < n; i++)
            write(b[i]), putchar(' ');
        return (0^_^0);
    }
}
int main()
{
    return zyt::work();
}

三、加减乘除

加减法:直接每项对应相加减。

乘法:这就是NTT的目的啊喂!

除法:如果不是带余除法直接乘逆元。下面着重介绍带余除法。

已知\(n\)次多项式\(F\)和\(m\)次多项式\(G\),求\(n-m\)次多项式\(Q\)和多项式\(R\)(\(R\)的次数\(deg_R\)小于\(m\)),满足:

\[F=QG+R\]

(未完待续咕咕咕……

原文地址:https://www.cnblogs.com/zyt1253679098/p/10226915.html

时间: 2024-11-05 12:33:33

【知识总结】多项式全家桶(一)(NTT、加减乘除和求逆)的相关文章

【知识总结】多项式全家桶(四)(快速幂和开根)

上一篇:[知识总结]多项式全家桶(三)(任意模数NTT) 推荐小恐龙的博客(参考资料):多项式开根 (本文中一切多项式运算默认在模 \(x_n\) 意义下进行) 一.快速幂 多项式快速幂?首先有一种很显然的方式是把整数快速幂里面的整数乘法替换成多项式乘法 NTT ,复杂度 \(O(n\log^2n)\) . 然而还有一种 \(O(n\log n)\) 的做法:要求 \(B=A^k\) ,相当于求 \(\log_A B=k\) ,用换底公式得 \(\log_A B=\frac{\ln B}{\ln

多项式全家桶

Include 多项式乘法 多项式求逆 多项式除法 多项式取模 多项式对数函数 多项式指数函数 多项式正弦函数 多项式余弦函数 #include<bits/stdc++.h> #define reg register int #define il inline #define fi first #define se second #define mk(a,b) make_pair(a,b) #define numb (ch^'0') using namespace std; typedef l

[模板] 多项式全家桶

注意:以下所有说明均以帮助理解模板为目的,不保证正确性. 多项式求逆 已知$A(x)$,求满足$A(x)B(x)=1\ (mod\ x^n)$的B(以下为了方便假设n是2的幂) 考虑倍增,假设已经求出$A(x)B_0(x)=1\ (mod\ x^{n/2})$ $$A(x)(B(x)-B_0(x))=0\ (mod\ x^{n/2})$$ $$(B(x)-B_0(x))=0\ (mod\ x^{n/2})$$ $$(B(x)-B_0(x))^2=0\ (mod\ x^n)$$ $$B^2(x)-

[算法学习] 多项式全家桶

多项式 一个\(n\)次多项式可以表示为\(A(x)=\sum_{i=0}^{n}a_i x^i\),另一个\(n\)次多项式可以表示为\(B(x)=\sum_{i=0}^{n}b_i x^i\). 多项式加法 将\(A(x)\)和\(B(x)\)相加,得到多项式\(C(x)=\sum_{i=0}^{n} (a_i+b_i) x^i\). 复杂度是\(O(n)\)的. 多项式乘法 将\(A(x)\)和\(B(x)\)相乘,得到多项式\(C(x)=\sum_{i=0}^{n}\sum_{j=0}^

前端工程师晋升课程 Vue全家桶+SSR+Koa2全栈开发美团网

第1章 课程导学这门课主讲以Vue SSR+Koa2全栈技术为目标,最终实现美团网项目.本章节旨在告诉大家我们会用到哪些技能.教学方法.课程内容分布.学习方法等.备注:我们会涉及Vue2.5.Nuxt.Koa2.element-ui.Mongodb等 1-1 课程导学第2章 Vue基础知识整个SSR部分都是用的Vue框架,需要给初级用户讲解Vue的基础语法,不会让他们在学习实战的时候感到迷茫,这个章节会通过vue-cli搭建一个简单的demo,让大家快速的掌握Vue的基础应用,即使他没有学习过.

【转】Spring全家桶

Spring框架自诞生以来一直备受开发者青睐,有人亲切的称之为:Spring 全家桶.它包括SpringMVC.SpringBoot.Spring Cloud.Spring Cloud Dataflow等解决方案. 很多研发人员把spring看作心目中最好的java项目,没有之一. 所以这是重点也是难点,工作中必须会,面试时肯定考. 那么,今天花费20分钟,梳理Spring框架相关知识. Spring系列包含非常多的项目,可以满足java开发中的方方面面. 先来看常用框架的知识点汇总,如图: Ⅰ

Vue全家桶高仿小米商城

大家好,我是河畔一角,时隔半年再次给大家带来一门重量级的实战课程:<Vue全家桶高仿小米商城>,现在很多公司都在参与到商城的构建体系当中,因此掌握一套商城的标准开发体系非常重要:商城的开始时非常复杂的,包括众多的页面设计和交互,以及非常丰富的知识点,所以一旦学会商城开发,其它系统完全不在话下. 下面给大家介绍一下小米商城包含的知识点和内容: 商城的页面流程: 商城组件部分: NavHeader(导航头组件).NavFooter(导航底部组件).ServiceBar(服务条组件).Product

Vue全家桶实战 从零独立开发企业级电商系统

Vue全家桶实战 从零独立开发企业级电商系统(免费升级Vue3.0)现阶段,电商系统随处可见,具有很大市场潜力:同时因为商城系统复杂,涉及到丰富的知识点,如果能进行电商系统的开发,其它各类型的前端系统也能掌握.本课程以Vue全家桶作为主要的技术体系,模拟小米商城,带大家从0开始开发网页和交互功能.你能进行完整的项目架构.体会页面开发的全流程.学习丰富的技术栈和各类组件知识,还能了解Git.动画.开发调试等方面的知识.同时项目本身具有很强的实用性,稍作修改,便能"为我所用".相信此课程能

react全家桶从0搭建一个完整的react项目(react-router4、redux、redux-saga)

react全家桶从0到1(最新) 本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目.文中针对react.webpack.babel.react-route.redux.redux-saga的核心配置会加以讲解,通过这个项目,可以系统的了解react技术栈的主要知识,避免搭建一次后面就忘记的情况. 从webpack开始 思考一下webpack到底做了什么事情?其实简单来说,就是从入口文件开始,不断寻找依赖,同时为了解析各种不同的文件加载相应的loader,最后生成我们希望的