学习笔记:FFT与拉格朗日插值
多项式的表示形式#
系数表示与点值表示#
假设
-
个点值可以表示一个 次多项式。 -
在点值表示下
次多项式的乘法复杂的为 。设
,则 。
复数与单位根#
复数的指数形式#
欧拉公式:
。
单位根#
单位根在复平面上等分单位圆。
单位根的性质:
。
快速傅里叶变换(Fast Fourier Transform)#
离散傅里叶变换(Discrete Fourier Transform)#
将多项式
不妨令
把原式拆成奇偶两部分,即
令
则
对于
对于另一半的点值,可用
具体的讲,当
可以直观看出,以这种方式将系数表示转化为点表示的时间复杂度为
对于具体实现
-
分治(递归,常数大)。
-
蝴蝶变换(bit-reversal permutation)(非递归,常数小)。
观察上图第一行和最后一行各项的二进制表示,互相颠倒。
因此可以预处理最后一行的系数,自下而上递推。
逆离散傅里叶变换(Inverse DFT)#
将多项式的点值表示
设
则 IDFT 相当于求解方程
如果我们能够求出
设
其中
相当于等比数列求和
令
相当于给定
例题#
A * B Problem Plus#
题意:给定
将
SP8372 TSUM#
题意:给定长度为
构造多项式
那么
这样求得的数对不一定满足偏序关系,也不一定互不相同。
偏序关系很好处理,最后将答案除上
现要除去存在位置相同的数对,考虑容斥。
性质
-
减去满足三个性质中一个的方案。
钦定两个位置相等。
令
,产生的方案为 。 -
加上满足三个性质中两个的方案。
。令
,这部分的方案为 。 -
减去满足三个性质中三个的方案。
与第二部分相同。
则最终答案
可以先对
FFT 在字符串匹配中的应用#
普通的单模式串匹配#
问题概述:给定模式串
定义匹配函数
定义完全匹配函数
当且仅当
令
于是
暴力展开:
第一项为常数。
第二项可以预处理
第三项等效于求
则
把字符串当作多项式,即
则
最后检验多项式
带通配符的单模式串匹配#
问题概述:给定模式串
总结思路:
- 定义匹配函数。
- 定义完全匹配函数。
- 快速计算每一位的完全匹配函数(翻转模式串化为卷积)。
设通配符的数值为
定义匹配函数
则完全匹配函数
将
暴力展开:
把右边写成多项式乘积的形式,则
例题#
P4173 残缺的字符串#
模板题。
#include<bits/stdc++.h>
using namespace std;
using namespace numbers;
using ll = long long;
constexpr int N = 6e5 + 5, tot = 1 << 19;
struct Complex {
double x, y;
Complex operator + (const Complex &o) const {
return {x + o.x, y + o.y};
}
Complex operator - (const Complex &o) const {
return {x - o.x, y - o.y};
}
Complex operator * (const Complex &o) const {
return {x * o.x - y * o.y, x * o.y + y * o.x};
}
} a[N], b[N], c[N];
int rev[N];
void fft(Complex a[], int Inv) {
for(int i = 0; i < tot; ++ i) {
if(i < rev[i]) {
swap(a[i], a[rev[i]]);
}
}
for(int mid = 1; mid < tot; mid <<= 1) {
auto w1 = Complex({cos(pi / mid), Inv * sin(pi / mid)});
for(int i = 0; i < tot; i += mid * 2) {
auto wk = Complex({1, 0});
for(int j = 0; j < mid; ++ j, wk = wk * w1) {
auto x = a[i + j], y = a[i + j + mid];
a[i + j] = x + wk * y;
a[i + j + mid] = x - wk * y;
}
}
}
if(Inv == -1) {
for(int i = 0; i < tot; ++ i) {
a[i].x /= tot;
}
}
}
string s, t;
int m, n, A[N], B[N];
int main() {
cin.tie(0)->sync_with_stdio(0);
cin >> m >> n >> s >> t;
reverse(s.begin(), s.end());
for(int i = 0; i < m; ++ i) {
A[i] = s[i] == '*' ? 0 : s[i] - 'a' + 1;
}
for(int i = 0; i < n; ++ i) {
B[i] = t[i] == '*' ? 0 : t[i] - 'a' + 1;
}
for(int i = 0; i < tot; ++ i) {
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << 18);
}
for(int i = 0; i < tot; ++ i) {
a[i] = Complex({A[i] * A[i] * A[i], 0});
b[i] = Complex({B[i], 0});
}
fft(a, 1), fft(b, 1);
for(int i = 0; i < tot; ++ i) {
c[i] = c[i] + a[i] * b[i];
}
for(int i = 0; i < tot; ++ i) {
a[i] = Complex({A[i], 0});
b[i] = Complex({B[i] * B[i] * B[i], 0});
}
fft(a, 1), fft(b, 1);
for(int i = 0; i < tot; ++ i) {
c[i] = c[i] + a[i] * b[i];
}
for(int i = 0; i < tot; ++ i) {
a[i] = Complex({A[i] * A[i], 0});
b[i] = Complex({B[i] * B[i], 0});
}
fft(a, 1), fft(b, 1);
for(int i = 0; i < tot; ++ i) {
c[i] = c[i] - Complex({2, 0}) * a[i] * b[i];
}
fft(c, -1);
vector<int> ans;
for(int i = m - 1; i < n; ++ i) {
ll v = c[i].x + 0.5;
if(v == 0) {
ans.push_back(i - m + 2);
}
}
cout << ans.size() << '\n';
for(int x : ans) cout << x << ' ';
return 0;
}
CF528D#
题意:给定只含 ATGC
的两个字符串
记
预处理第
定义完全匹配函数
单独考虑每个字符,记
按照套路将
则
令
拉格朗日插值#
拉格朗日插值定理#
构造一个多项式
满足
待定系数,
带入
-
与
的关系?多项式模多项式:如果
,其中 的次数小于 ,则记做 。多项式互质:两个多项式的公因式只有常数,则称这两个多项式互质。
对于任意
:有同余方程组
显然有模数两两互质,满足中国剩余定理的使用条件。
-
暴力实现:先算
,再求 次 ,复杂度 。 -
特殊情况1:只要求一个
,复杂度 。 -
特殊情况2:只要求一个
,满足 ,复杂度 (可以预处理)。
例题#
CF622F#
题意:求
证明:
对于第二类斯特林数,满足公式
那么
有组合恒等式
则
所以求出
Polynomia#
题意:
先对
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?