首页 > 其他分享 >NTT 学习笔记

NTT 学习笔记

时间:2024-08-07 11:21:45浏览次数:12  
标签:return int NTT 笔记 学习 Poly operator ModInt omega

NTT

前置知识:FFT

NTT,中文“快速数论变换”,是 FFT 在数论领域上的实现,比 FFT 更快,应用更广。

对于 FFT,因为其涉及到复数操作,对于某些需要取模的题不再适用。并且因为需要求正弦与余弦,使用时难以避免精度误差。这时就需要用到 NTT 来解决问题了。

我们知道 FFT 的实现是在复平面上找到了 \(n\) 个不同的点 \(\omega^0_n, \omega^1_n, \dots, \omega^{n - 1}_n\)。由于现在需要解决取模的问题,因此我们考虑在剩余系中找 \(n\) 个等价的点。

原根

我们可以使用原根找到这 \(n\) 个数。由于原根通常较小,所以可以暴力求原根。

假设要取模的奇素数为 \(p\),再设 \(p = qn + 1\) 且 \(n \mid (p - 1)\)。

设 \(g\) 为模 \(p\) 意义下的原根,根据费马小定理和欧拉定理得:\(g^{nq}\equiv g^{\varphi(p)}\equiv 1 \pmod p\)。其中 \(\varphi\) 为欧拉函数,由于 \(p\) 是奇数,故 \(\varphi(p) = p - 1\)。

为了验证原根与单位根的相似性质,不妨令 \(x^i_n = g^{iq} = \left(g^\frac{p - 1}{n}\right)^i\),看做和 \(\omega^i_n\) 等价。我们接下来验证 \(x^i_n\) 是否与 \(\omega^i_n\) 等价。

  • 乘法:\(\omega^i_n \times \omega^j_n = \omega^{i + j}_n\)。对于 \(x^i_n\) 显然成立。

  • 周期性:\(\omega^i_n = \omega^{i + n}_n\)。由于 \(x^{n}_n = g^{nq} \equiv 1 \pmod p\),所以 \(x^{i + n}_n = x^i_n \times x^n_n = x^i_n\),成立。

  • 互异性:\(\omega^0_n, \omega^1_n, \dots, \omega^{n - 1}_n\) 互不相同。根据原根的性质可以很容易得到 \(x^0_n, x^1_n, \dots, x^{n - 1}_n\) 不同。

  • 消去引理:\(\forall n \in \mathbb{N}, k \in \mathbb{N}, d \in \mathbb{N}^{*}\),有 \(\omega^{dk}_{dn} = \omega^{k}_n\)。证明:\(x^{dk}_{dn} = g^{dk \cdot \frac{p - 1}{dn}} = \left(g^{\frac{p - 1}{n}}\right)^k=x^k_n\)。

  • 折半引理:\(\forall n \in \mathbb{N}, k \in \mathbb{N}\) 且 \(n\) 为偶数,有 \((\omega^{k + n/ 2}_n)^2 = \omega^k_{n / 2}\)。首先 \((x^{k + n/ 2}_n)^2 = x^{2k+n}_n\),因为有周期性 \(x^{2k+n}_n = x^{2k}_n\),再由消去引理得到 \(x^{2k}_n = x^{k}_{n/2}\)。

可以发现,因为 \(x^i_n\) 同样满足消去引理与折半引理,所以后面对多项式的推导中 \(x^i_n\) 与 \(\omega^i_n\) 完全等价。

模数

不是所有模数都可以使用 NTT(至少不能用朴素的 NTT)。

FFT 中,为了方便使用折半引理,我们强制将 \(n \leftarrow 2^{\left\lceil\log_2 n\right\rceil}\),这样的话我们的模数必须满足 \(p = q2^k + 1\),其中 \(q\) 是奇数,必须有 \(n \le 2^k\)。后面可能会收录一些 NTT 常用模数,平常可以用 \(998244353\)。

INTT

将 \(x^{-i}_n\) 换成 \(\frac{1}{x^i_n}\) 后用逆元即可,除以 \(n\) 时也用逆元。

代码:

template<class T>
T power(T a, i64 b) {
    T res = 1;
    for (; b; b >>= 1, a *= a) {
        if (b & 1)
            res *= a;
    }
    return res;
}
template<const i64 P>
class ModInt {
public:
    i64 x;
    static i64 Mod;

    ModInt() : x{0} {}
    ModInt(int _x) : x{(_x % getMod() + getMod()) % getMod()} {}
    ModInt(i64 _x) : x{(_x % getMod() + getMod()) % getMod()} {}

    static void setMod(i64 Mod_) {
        Mod = Mod_;
    }
    static i64 getMod() {
        return !P ? Mod : P;
    }
    explicit constexpr operator int() const {
        return x;
    }

    ModInt &operator += (ModInt a) & {
        x = x + a.x >= getMod() ? x + a.x - getMod() : x + a.x;
        return (*this);
    }
    ModInt &operator -= (ModInt a) & {
        x = x - a.x < 0 ? x - a.x + getMod() : x - a.x;
        return (*this);
    }
    ModInt &operator *= (ModInt a) & {
        (x *= a.x) %= getMod();
        return (*this);
    }
    constexpr ModInt inv() {
        return power((*this), getMod() - 2);
    }
    ModInt &operator /= (ModInt a) & {
        return (*this) *= a.inv();
    }
    friend ModInt operator + (ModInt lhs, ModInt rhs) {
        return lhs += rhs;
    }
    friend ModInt operator - (ModInt lhs, ModInt rhs) {
        return lhs -= rhs;
    }
    friend ModInt operator * (ModInt lhs, ModInt rhs) {
        return lhs *= rhs;
    }
    friend ModInt operator / (ModInt lhs, ModInt rhs) {
        return lhs /= rhs;
    }

    friend std::istream &operator >> (std::istream &is, ModInt &p) {
        return is >> p.x;
    }
    friend std::ostream &operator << (std::ostream &os, ModInt p) {
        return os << p.x;
    }
    int operator !() {
        return !x;
    }
    friend bool operator == (ModInt lhs, ModInt rhs) {
        return lhs.x == rhs.x;
    }
    friend bool operator != (ModInt lhs, ModInt rhs) {
        return lhs.x != rhs.x;
    }
    ModInt operator -() {
        return ModInt(getMod() - x);
    }
    ModInt &operator ++() & {
        ++x;
        return *this;
    }
    ModInt operator ++(int) {
        ModInt temp = *this;
        ++*this;
        return temp;
    }
} ;
template<>
i64 ModInt<0>::Mod = 998244353;
const int P = 167772161, g = 3;
using Z = ModInt<P>;
struct Comb {
    int n;
    vector<Z> _fac;
    vector<Z> _invfac;
    vector<Z> _inv;
    Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
    Comb(int n) : Comb() {init(n);}
    void init(int m) {
        m = min<int>(m, Z::getMod() - 1);
        if (m <= n) return;
        _fac.resize(m + 1);
        _invfac.resize(m + 1);
        _inv.resize(m + 1);
        for (int i = n + 1; i <= m; i++) _fac[i] = _fac[i - 1] * i;
        _invfac[m] = _fac[m].inv();
        for (int i = m; i > n; i--) {
            _invfac[i - 1] = _invfac[i] * i;
            _inv[i] = _invfac[i] * _fac[i - 1];
        } n = m;
    }
    Z fac(int m) {if (m > n) init(2 * m); return _fac[m];}
    Z invfac(int m) {if (m > n) init(2 * m); return _invfac[m];}
    Z inv(int m) {if (m > n) init(2 * m); return _inv[m];}
    Z binom(int n, int m) {return n < m || m < 0 ? 0 : fac(n) * invfac(m) * invfac(n - m);}
} comb;
std::vector<int> rev;
void ExtendRev(int n) {
    int m = rev.size();
    rev.resize(n);
    int s = __builtin_ctz(n) - 1;
    for (int i = 0; i < n; ++i)
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << s);
}

template<const int P, const int g>
struct Poly : public std::vector<ModInt<P>> {
    using V = ModInt<P>;
    int invg = V(g).inv().x;

    Poly() : std::vector<V>() {}
    Poly(int n) : std::vector<V>(n, V {}) {}
    Poly(int n, V x) : std::vector<V>(n, x) {}
    Poly(std::vector<V> _a) : std::vector<V>(_a) {}
    Poly(std::initializer_list<V> _a) : std::vector<V>(_a) {}
    template<class InputIt, class = std::_RequireInputIter<InputIt>>
    Poly(InputIt first, InputIt last) : std::vector<V>(first, last) {}

    std::vector<V> trunc(int n) {
        auto f = *this;
        f.resize(n);
        return f;
    }
    void dft(int Root = g) {
        int n = this->size();
        ExtendRev(n);
        for (int i = 0; i < n; ++i)
            if (i < rev[i]) {
                std::swap((*this)[i], (*this)[rev[i]]);
            }

        for (int k = 1; k < n; k <<= 1) {
            V wn = power(V(Root), (P - 1) / (2 * k));
            for (int i = 0; i < n; i += 2 * k) {
                V w = 1;
                for (int j = 0; j < k; ++j, w *= wn) {
                    auto x = (*this)[i + j], y = (*this)[i + j + k] * w;
                    (*this)[i + j] = x + y;
                    (*this)[i + j + k] = x - y;
                }
            }
        }
    }
    void idft() {
        dft(invg);
        V invn = V(int(this->size())).inv().x;
        for (auto &x : *this)
            x *= invn;
    }

    friend Poly operator * (Poly a, V b) {
        for (auto &x : a)
            x *= b;
        return a;
    }
    friend Poly operator * (Poly a, Poly b) {
        int m = a.size() + b.size() - 1, n = a.size() + b.size();
        for (; n != (n & -n); ++n) ;
        a.resize(n);
        b.resize(n);

        a.dft();
        b.dft();
        for (int i = 0; i < n; ++i) 
            a[i] *= b[i];
        a.idft();

        return a.trunc(m);
    }
    friend Poly operator + (Poly a, Poly b) {
        if (a.size() < b.size())
            std::swap(a, b);
        for (int i = 0; i < b.size(); ++i)
            a[i] += b[i];
        return a;
    }
    Poly operator -() {
        Poly a = *this;
        for (int i = 0; i < a.size(); ++i)
            a[i] = -a[i];
        return a;
    }
    friend Poly operator - (Poly a, Poly b) {
        return a + -b;
    }
} ;
using Pol = Poly<P, g>;

标签:return,int,NTT,笔记,学习,Poly,operator,ModInt,omega
From: https://www.cnblogs.com/CTHOOH/p/18187174

相关文章

  • 零基础学习人工智能—Python—Pytorch学习(一)
    前言其实学习人工智能不难,就跟学习软件开发一样,只是会的人相对少,而一些会的人写文章,做视频又不好好讲。比如,上来就跟你说要学习张量,或者告诉你张量是向量的多维度等等模式的讲解;目的都是让别人知道他会这个技术,但又不想让你学。对于学习,多年的学习经验,和无数次的回顾学习过程,都......
  • pytorch深度学习分类代码简单示例
    train.py代码如下importtorchimporttorch.nnasnnimporttorch.optimasoptimmodel_save_path="my_model.pth"#定义简单的线性神经网络模型classMyModel(nn.Module):def__init__(self):super(MyModel,self).__init__()self.output=n......
  • 感恩回馈粉丝福利,AI大模型100+300学习视频&PDF,白嫖的机会来了!
    近两个月以来内收获了许多粉丝,在此非常感谢,筹划好几天准备了对大家有帮助和提升的相关资源,因为许多限制,但为了感谢大家对我认可和支持,,感谢每一个支持的我的粉丝,起码的见面礼还是得有的,后面有白嫖的方式。作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。至于......
  • 强化学习性能测试方法:取最后10个epoch的testing epoch的均值 —— 强化学习中的一种性
    参考:https://www.cnblogs.com/devilmaycry812839668/p/17813337.htmlTheActor-MimicandexpertDQNtrainingcurvesfor100trainingepochsforeachofthe8games.Atrainingepochis250,000framesandforeachtrainingepochweevaluatethenetworkswith......
  • Android开发基础02-零基础学习Android指南
    学习路线1.理解Android开发基础1.1理解Android平台架构先从高层次上了解Android操作系统的架构,包括应用层、应用框架层、库和Android运行时、Linux内核。了解这些层次及其作用,会帮你更好地理解Android的工作原理。1.2学习Java乐Kotlin语言Java和Kotlin......
  • 后端开发学习敏捷需求-->专题的目标与价值成效
    专题的目标与价值成效什么是专题公司或企业为了抓住业务机会或者解决痛点问题,而采取的具体的行动和举措专题的目标分析1.业务调研了解目标的预期利用5W2H来进行专题分析what——是什么?目的是什么?作什么工作?专题是什么专题产生的背景是什么专题的目标是什么,要达到怎样......
  • PostgreSQL学习之pg_recvlogical与pgoutput的使用
        参考:        pg内功修炼:逻辑复制_pgoutput-CSDN博客        PG原生解码工具pg_recvlogical的使用-在脑裂时帮我们找回丢失的数据-腾讯云开发者社区-腾讯云(tencent.com)        postgresql数据库的原生解码插件pg_recvlogical可以将wal......
  • Java中对数组的学习
    数组的概念目录数组的概念声明数组变量创建数组处理数组数组作为函数的参数数组作为函数的返回值数组对于每一门编程语言来说都是重要的数据结构之一,当然不同语言对数组的实现及处理也不尽相同。Java语言中提供的数组是用来存储固定大小的同类型元素。你可以声明一个数组变......
  • 网课-动态规划学习笔记2
    记忆化搜索记忆化搜索是一种DP的实现方法。相同点:DP中同一局部问题只计算一次——搜索的记忆化不处理对答案没有贡献的情况——对应搜索的剪枝不同点:遍历顺序优化复杂度。按数组顺序进行的DP,经常可以配合一些优化技巧进一步降低复杂度。“DP是一种算法,......
  • LabVIEW的ActorFramework笔记
    1前置知识储备自分布式计算出现以来,业界已经开始广泛研究基于消息传递编程模型的解决方案。关于消息传递,Wikipedia描述其广泛定义主要包括:远程过程调用(RemoteProcedureCalls,RPC)和消息传递接口(MessagePassingInterface,MPI)。但是,如今我们所谈到的消息传递,通常是指acto......