C++ 单位根反演(Roots of Unity Filter)全解析

C++ 单位根反演(Roots of Unity Filter)全解析

前言

单位根反演 是组合数学、数论、生成函数、多项式算法中最核心的等价变换公式 ,专门解决整除性判定、模 k 计数、下标取模筛选、周期性求和 问题。它能把「对下标 i ≡ r (mod k) 求和」这种带取模限制的求和,转化为无限制普通求和,从而大幅简化计算。

在算法竞赛(ACM/NOI/CSP)、密码学、信号处理、C++ 高性能数学库、NTT 相关算法里,单位根反演是必备工具。它的本质是:用复数/模意义下单位根,构造"筛选器",精准提取满足同余条件的项

本文从数学定义→公式推导→几何意义→经典题型→C++ 完整实现全覆盖,理论严谨、代码可直接运行。


目录

  1. 单位根基础(复数域 + 模素数域)
  2. 单位根反演公式核心推导
  3. 公式直观理解:筛选器原理
  4. 最常用 3 大模板公式
  5. 典型应用场景(取模求和、整除计数、循环卷积)
  6. C++ 完整实现(复数版 + 模素数 NTT 版)
  7. 例题解析与代码运行演示
  8. 复杂度与工程优化

一、前置知识:n 次单位根

1.1 复数域单位根

n 次单位根:满足 ( z^n = 1 ) 的复数。

主 n 次单位根:

\\omega_n = e\^{\\frac{2\\pi i}{n}} = \\cos\\frac{2\\pi}{n} + i\\sin\\frac{2\\pi}{n}

核心性质:

  1. ( \omega_n^n = 1 )
  2. ( \omega_n^k ) 两两不同
  3. 正交性(反演基础):

    \\frac{1}{n}\\sum_{j=0}\^{n-1} \\omega_n\^{j\\cdot k} = \\begin{cases} 1 \& n\\mid k \\ 0 \& else \\end{cases}

1.2 模素数域单位根(算法中最常用)

算法里不用复数 ,用模素数域 ,通过原根构造单位根:

设模数 ( P ) 是质数,( g ) 是原根,则:

\\omega_n = g\^{\\frac{P-1}{n}}

要求:n | (P-1)

常用模数:

  • 998244353(原根3)
  • 1004535809(原根3)

二、单位根反演核心公式(最重要)

2.1 基础公式(正交性)

\\boxed{\\frac{1}{k} \\sum_{j=0}\^{k-1} \\omega_k\^{j\\cdot t} = \[k \\mid t\]}

含义:

  • 若 t 是 k 的倍数 → 结果 = 1
  • 否则 → 结果 = 0

这就是整除判定器

2.2 反演公式(求和筛选)

我们要计算:

S = \\sum_{n} \[k \\mid n\] \\cdot a_n

代入单位根反演:

S = \\frac{1}{k} \\sum_{j=0}\^{k-1} \\sum_{n} a_n \\cdot \\omega_k\^{jn}

交换求和顺序:

S = \\frac{1}{k} \\sum_{j=0}\^{k-1} F(\\omega_k\^j)

其中 ( F(x) = \sum a_n x^n ) 是生成函数。

一句话总结:单位根反演 = 把取模限制,变成对单位根的求值。

2.3 更一般:同余 n ≡ r (mod k)

\\sum_{n \\equiv r \\pmod k} a_n = \\frac{1}{k}\\sum_{j=0}\^{k-1} \\omega_k\^{-jr} F(\\omega_k\^j)

这是算法竞赛最常用模板


三、直观理解:单位根 = 筛选器

你可以把单位根反演看作:

  • 构造 k 个"频率"
  • 只有下标是 k 倍数的项会共振保留
  • 其余项全部抵消为 0

它就是数学里的带通滤波器


四、单位根反演三大标准模板(背会直接用)

模板 1:求下标被 k 整除的和

\\sum_{k\\mid n} a_n = \\frac1k \\sum_{j=0}\^{k-1} F(\\omega_k\^j)

模板 2:求下标 ≡ r (mod k) 的和

\\sum_{n\\equiv r\\pmod k} a_n = \\frac1k \\sum_{j=0}\^{k-1} \\omega_k\^{-jr} F(\\omega_k\^j)

模板 3:计数 f(n) 中满足 n mod k = r 的数量

ans = \\frac1k \\sum_{j=0}\^{k-1} \\omega_k\^{-jr} \\sum_n f(n)\\omega_k\^{jn}


五、典型应用场景

  1. 求 n! 中指数是 k 的倍数的项和
  2. 斐波那契数列中满足下标 ≡0/1/2 mod k 的和
  3. 组合数取模筛选
  4. 循环卷积、周期序列求和
  5. 生成函数限定下标提取
  6. NTT 加速的取模类 DP 优化

凡是题目出现:

"mod k"、"被 k 整除"、"下标同余"、"周期为 k"

99% 可以用单位根反演。


六、C++ 完整实现

版本 1:复数版(教学、直观)

cpp 复制代码
#include <iostream>
#include <vector>
#include <complex>
#include <cmath>
using namespace std;

typedef complex<double> cd;
const double PI = acos(-1);

// 主 n 次单位根
cd omega(int n, int k) {
    return exp(cd(0, 2 * PI * k / n));
}

// 单位根反演:求 a_n 中 n ≡ r (mod k) 的和
template<typename Func>
double roots_of_unity_filter(Func&& F, int k, int r) {
    cd res = 0;
    for (int j = 0; j < k; j++) {
        cd w = omega(k, j);
        cd coeff = exp(cd(0, -2 * PI * j * r / k)); // w^(-jr)
        res += coeff * F(w);
    }
    return res.real() / k;
}

// 示例:F(x) = 1 + x + x^2 + x^3 + x^4 + x^5
double test_F(cd x) {
    return 1.0 + x + x*x + x*x*x + x*x*x*x + x*x*x*x*x;
}

int main() {
    int k = 3, r = 0;
    double ans = roots_of_unity_filter(test_F, k, r);
    // 下标 0,3 → 1+1=2
    cout << "n ≡ 0 mod 3 的和 = " << ans << endl;
    return 0;
}

版本 2:模素数版(算法竞赛正式版)

真正比赛/工程用这个

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

const int MOD = 998244353;
const int G = 3;

long long qpow(long long a, long long b) {
    long long res = 1;
    while (b) {
        if (b & 1) res = res * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return res;
}

// 求 k 次单位根
long long get_omega(int k) {
    return qpow(G, (MOD - 1) / k);
}

// 单位根反演求 sum [n ≡ r mod k] a_n
long long solve(const vector<long long>& a, int k, int r) {
    long long w = get_omega(k);
    long long inv_k = qpow(k, MOD - 2);
    long long ans = 0;

    for (int j = 0; j < k; j++) {
        long long wj = qpow(w, j);
        long long c = qpow(wj, (k - r) % k); // w^(-jr)

        long long F = 0;
        long long p = 1;
        for (auto v : a) {
            F = (F + v * p) % MOD;
            p = p * wj % MOD;
        }

        ans = (ans + c * F) % MOD;
    }
    ans = ans * inv_k % MOD;
    return ans;
}

int main() {
    vector<long long> a = {1,1,1,1,1,1}; // 1+x+x^2+x^3+x^4+x^5
    int k = 3, r = 0;
    cout << solve(a, k, r) << endl; // 输出 2
    return 0;
}

七、公式推导(最关键的一步)

我们要证明:

\\frac1k\\sum_{j=0}^{k-1}\\omega_k^{jn} = \[k\\mid n

]

证明:

设 ( n = qk + t )

若 ( t=0 ):

\\frac1k\\sum_{j=0}\^{k-1} 1 = 1

若 ( t≠0 ):

\\frac1k \\cdot \\frac{\\omega_k^{kn}-1}{\\omega_k^n-1} = \\frac1k \\cdot 0 = 0

证毕。


八、复杂度分析

  • 暴力筛选:( O(n) )
  • 单位根反演:( O(k \cdot M) )
    • M 是生成函数求值代价
  • 若配合 NTT:可做到 ( O(n\log n) )

k 通常很小(如 2,3,4,5),速度极快


九、适用条件

  1. 必须存在 k 次单位根
  2. 模意义下要求 ( k \mid (P-1) )
  3. 生成函数可快速求值
  4. 问题包含同余、整除、周期

十、总结(最核心 3 句话)

  1. 单位根反演 = 用单位根构造整除筛选器
  2. 核心公式:

    \\frac1k\\sum_{j=0}^{k-1}\\omega_k^{jn} = \[k\\mid n

    ]
  3. 凡是题目出现 mod k、被 k 整除、下标同余,直接套单位根反演。
相关推荐
1104.北光c°1 小时前
深度剖析 Spring 灵魂:IOC 容器与自动装配的原理、设计与实现
java·开发语言·笔记·后端·spring·rpc·ioc
Volunteer Technology1 小时前
Spring6.0新特性
java·开发语言·spring
pixelpilot11 小时前
微软常用运行库directx修复工具(directx repair)2026版directx下载下载安装教程
java·开发语言·其他·microsoft
cany10001 小时前
C++进阶 -- std::deque‌ 和 ‌std::list
开发语言·c++
曾几何时`1 小时前
Go(二)Goroutine及GMP模型
开发语言·后端·golang
AD钙奶-lalala1 小时前
kotlin反射
android·开发语言·kotlin
2301_789015621 小时前
Lnux权限
linux·开发语言·c++·权限
江湖中的阿龙1 小时前
Go语言零基础入门教程(一)环境搭建与基础入门
开发语言·后端·golang
集成显卡9 小时前
Rust实战七 |基于带 colored 颜色文字控制台的批量文件删除工具
开发语言·后端·rust