卷积

给定向量:a=(a0,a1,...,an−1),b=(b0,b1,...,bn−1)

向量和:a+b=(a0+b0,a1+b1,...,an−1+bn−1)

数量积(内积、点积):a⋅b=a0b0+a1b1+...+an−1bn−1

卷积:a⊗b=(c0,c1,...,c2n−2),其中ck=∑i+j=k(aibj)

例如:cn−1=a0bn−1+a1bn−2+...+an−2b1+an−1b0

卷积的最典型的应用就是多项式乘法(多项式乘法就是求卷积)。以下就用多项式乘法来描述、举例卷积与DFT。

关于多项式

对于多项式A(x),系数为ai,设最高非零系数为ak,则其次数就是k,记作degree(A)=k。任何大于k的整数都是A(x)的次数界

多项式的系数表达方式:A(x)=a0+a1x+a2x2+...+an−1xn−1=∑n−1i=0ajxj(次数界为n)。

则多项式的系数向量即为a=(a0,a1,...,an−1)。

多项式的点值表达方式:{(x0,y0),(x1,y1),...,(xn−1,yn−1)},其中xk各不相同,yk=A(xk)。

离散傅里叶变换(DFT)

离散傅里叶变换(Discrete Fourier Transform,DFT)。在信号处理很重要的一个东西,这里物理意义以及其他应用暂不予理睬。在多项式中,DFT就是系数表式转换成点值表示的过程。

快速傅里叶变换(FFT)

快速傅里叶变换(Fast Fourier Transformation,FFT):快速计算DFT的算法,能够在O(nlogn)的时间里完成DFT。FFT只是快速的求DFT的方法罢了,不是一个新的概念。 在ACM-ICPC竞赛中, FFT算法常被用来为多项式乘法加速。FFT与其逆变换IFFT类似,稍微加几行代码。

求FFT要用到复数。一个简单的模板:

struct Complex // 复数
{
double r, i;
Complex(double _r = 0, double _i = 0) :r(_r), i(_i) {}
Complex operator +(const Complex &b) {
return Complex(r + b.r, i + b.i);
}
Complex operator -(const Complex &b) {
return Complex(r - b.r, i - b.i);
}
Complex operator *(const Complex &b) {
return Complex(r*b.r - i*b.i, r*b.i + i*b.r);
}
};

递归实现FFT模板:来源

Complex* RecursiveFFT(Complex a[], int n)//n表示向量a的维数
{
if(n == 1)
return a;
Complex wn = Complex(cos(2*PI/n), sin(2*PI/n));
Complex w = Complex(1, 0);
Complex* a0 = new Complex[n >> 1];
Complex* a1 = new Complex[n >> 1];
for(int i = 0; i < n; i++)
if(i & 1) a1[(i - 1) >> 1] = a[i];
else a0[i >> 1] = a[i];
Complex *y0, *y1;
y0 = RecursiveFFT(a0, n >> 1);
y1 = RecursiveFFT(a1, n >> 1);
Complex* y = new Complex[n];
for(int k = 0; k < (n >> 1); k++)
{
y[k] = y0[k] + w*y1[k];
y[k + (n >> 1)] = y0[k] - w*y1[k];
w = w*wn;
}
delete a0;
delete a1;
delete y0;
delete y1;
return y;
}

非递归实现。模板:(来源忘了)

void change(Complex y[], int len) // 二进制平摊反转置换 O(logn)
{
int i, j, k;
for (i = 1, j = len / 2;i < len - 1;i++)
{
if (i < j)swap(y[i], y[j]);
k = len / 2;
while (j >= k)
{
j -= k;
k /= 2;
}
if (j < k)j += k;
}
}
void fft(Complex y[], int len, int on) //FFT:on=1; IFFT:on=-1
{
change(y, len);
for (int h = 2;h <= len;h <<= 1)
{
Complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h));
for (int j = 0;j < len;j += h)
{
Complex w(1, 0);
for (int k = j;k < j + h / 2;k++)
{
Complex u = y[k];
Complex t = w*y[k + h / 2];
y[k] = u + t;
y[k + h / 2] = u - t;
w = w*wn;
}
}
}
if (on == -1)
for (int i = 0;i < len;i++)
y[i].r /= len;
}

利用FFT求卷积

普通的计算多项式乘法的计算,时间复杂度O(n2)。而FFT先将多项式点值表示(O(nlogn)),在O(n)下完成对点值的乘法,再以O(nlogn)完成IFFT,重新得到系数表示。

步骤一(补0)

在两个多项式前面补0,得到两个2n次多项式,设系数向量分别为v1和v2。

步骤二(求值)

使用FFT计算f1=DFT(v1)和f2=DFT(v2)。则f1与f2为两个多项式在2n次单位根处的取值(即点值表示)。

步骤三(乘法)

把f1与f2每一维对应相乘,得到f,代表对应输入多项式乘积的点值表示。

步骤四(插值)

使用IFFT计算v=IDFT(f),其中v就是乘积的系数向量。

综上

a⊗b=IDFT2n(DFT2n(a)⋅DFT2n(b)),即:a⊗b=DFT−12n(DFT2n(a)⋅DFT2n(b))

A(x1)⊗B(x2):

fft(x1, len, 1);
fft(x2, len, 1);
for (int i = 0;i < len;i++) {
x[i] = x1[i] * x2[i];
}
fft(x, len, -1);

例题

1.2016 acm香港网络赛 A题 A+B Problem

网上的代码(当时没保留出处。。。)

#include <algorithm>
#include <cstring>
#include <string.h>
#include <iostream>
#include <list>
#include <map>
#include <set>
#include <stack>
#include <string>
#include <utility>
#include <vector>
#include <cstdio>
#include <cmath> #define LL long long
#define N 200005
#define INF 0x3ffffff using namespace std; const double PI = acos(-1.0); struct Complex // 复数
{
double r, i;
Complex(double _r = 0, double _i = 0) :r(_r), i(_i) {}
Complex operator +(const Complex &b)
{
return Complex(r + b.r, i + b.i);
}
Complex operator -(const Complex &b)
{
return Complex(r - b.r, i - b.i);
}
Complex operator *(const Complex &b)
{
return Complex(r*b.r - i*b.i, r*b.i + i*b.r);
}
}; void change(Complex y[], int len) // 二进制平摊反转置换 O(logn)
{
int i, j, k;
for (i = 1, j = len / 2;i < len - 1;i++)
{
if (i < j)swap(y[i], y[j]);
k = len / 2;
while (j >= k)
{
j -= k;
k /= 2;
}
if (j < k)j += k;
}
}
void fft(Complex y[], int len, int on) //DFT和FFT
{
change(y, len);
for (int h = 2;h <= len;h <<= 1)
{
Complex wn(cos(-on * 2 * PI / h), sin(-on * 2 * PI / h));
for (int j = 0;j < len;j += h)
{
Complex w(1, 0);
for (int k = j;k < j + h / 2;k++)
{
Complex u = y[k];
Complex t = w*y[k + h / 2];
y[k] = u + t;
y[k + h / 2] = u - t;
w = w*wn;
}
}
}
if (on == -1)
for (int i = 0;i < len;i++)
y[i].r /= len;
} const int M = 50000; // a数组所有元素+M,使a[i]>=0
const int MAXN = 800040; Complex x1[MAXN];
int a[MAXN / 4]; //原数组
long long num[MAXN]; //利用FFT得到的数组
long long tt[MAXN]; //统计数组每个元素出现个数 int main()
{
int n = 0; // n表示除了0之外数组元素个数
int tot;
scanf("%d", &tot);
memset(num, 0, sizeof(num));
memset(tt, 0, sizeof(tt)); int cnt0 = 0; //cnt0 统计0的个数
int aa; for (int i = 0;i < tot;i++)
{
scanf("%d", &aa);
if (aa == 0) { cnt0++;continue; } //先把0全删掉,最后特殊考虑0
else a[n] = aa;
num[a[n] + M]++;
tt[a[n] + M]++;
n++;
} sort(a, a + n);
int len1 = a[n - 1] + M + 1;
int len = 1; while (len < 2 * len1) len <<= 1; for (int i = 0;i < len1;i++) {
x1[i] = Complex(num[i], 0);
}
for (int i = len1;i < len;i++) {
x1[i] = Complex(0, 0);
}
fft(x1, len, 1); for (int i = 0;i < len;i++) {
x1[i] = x1[i] * x1[i];
}
fft(x1, len, -1); for (int i = 0;i < len;i++) {
num[i] = (long long)(x1[i].r + 0.5);
} len = 2 * (a[n - 1] + M); for (int i = 0;i < n;i++) //删掉ai+ai的情况
num[a[i] + a[i] + 2 * M]--;
/*
for(int i = 0;i < len;i++){
if(num[i]) cout<<i-2*M<<' '<<num[i]<<endl;
}
*/
long long ret = 0; int l = a[n - 1] + M; for (int i = 0;i <= l; i++) //ai,aj,ak都不为0的情况
{
if (tt[i]) ret += (long long)(num[i + M] * tt[i]);
} ret += (long long)(num[2 * M] * cnt0); // ai+aj=0的情况 if (cnt0 != 0)
{
if (cnt0 >= 3) { //ai,aj,ak都为0的情况
long long tmp = 1;
tmp *= (long long)(cnt0);
tmp *= (long long)(cnt0 - 1);
tmp *= (long long)(cnt0 - 2);
ret += tmp;
}
for (int i = 0;i <= l; i++)
{
if (tt[i] >= 2) { // x+0=x的情况
long long tmp = (long long)cnt0;
tmp *= (long long)(tt[i]);
tmp *= (long long)(tt[i] - 1);
ret += tmp * 2;
}
}
} printf("%lld\n", ret); return 0;
}

[笔记]ACM笔记 - 利用FFT求卷积(求多项式乘法)的更多相关文章

  1. FFT求卷积(多项式乘法)

    FFT求卷积(多项式乘法) 卷积 如果有两个无限序列a和b,那么它们卷积的结果是:\(y_n=\sum_{i=-\infty}^\infty a_ib_{n-i}\).如果a和b是有限序列,a最低的项 ...

  2. [笔记]ACM笔记 - 组合数

    一.高中数学公式复习 , (好吧这个没学过但是既然看到了就一并抄过来了) 二.快速求组合数取模C(n, m)%p 当n和p大小不同时方法有不同. 1. n很小,p随意,p不需要为素数 1) 原理 使用 ...

  3. [笔记]ACM笔记 - 自用模板

    长期更新. 快速幂 lld pow_mod(lld a, lld b, const int &pr) { lld ans = 1; while (b) { if (b & 1) ans ...

  4. [笔记]ACM笔记 - 排序小技巧

    Description 一个数组,要求先对前n个数字排序(以方便后续操作):又要求对前n+i个数字排序:又要求对前n+j - 前n+k个数字排序(i.j.k的大小远小于n,且i.j.k间没有大小关系) ...

  5. 多项式乘法(FFT)模板 && 快速数论变换(NTT)

    具体步骤: 1.补0:在两个多项式最前面补0,得到两个 $2n$ 次多项式,设系数向量分别为 $v_1$ 和 $v_2$. 2.求值:用FFT计算 $f_1 = DFT(v_1)$ 和 $f_2=DF ...

  6. 洛谷.3803.[模板]多项式乘法(FFT)

    题目链接:洛谷.LOJ. FFT相关:快速傅里叶变换(FFT)详解.FFT总结.从多项式乘法到快速傅里叶变换. 5.4 又看了一遍,这个也不错. 2019.3.7 叕看了一遍,推荐这个. #inclu ...

  7. Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现(转)

    Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现 zouxy09@qq.com http://blog.csdn.net/zouxy09          自己平时看了一些论文, ...

  8. 多项式乘法(FFT)学习笔记

    ------------------------------------------本文只探讨多项式乘法(FFT)在信息学中的应用如有错误或不明欢迎指出或提问,在此不胜感激 多项式 1.系数表示法  ...

  9. [模板] 多项式: 乘法/求逆/分治fft/微积分/ln/exp/幂

    多项式 代码 const int nsz=(int)4e5+50; const ll nmod=998244353,g=3,ginv=332748118ll; //basic math ll qp(l ...

随机推荐

  1. JS——操作内容、操作相关元素

    操作内容:普通元素.innerHTML = "值": 会把标记执行渲染普通元素.innerText = "值": 将值原封不动的展示出来,即使里面有标记 var ...

  2. CodeFirst的一些操作!!

    CodeFirst的一些操作!! 转载 2016-08-05 21:03:32 1 首先是codefirst怎么做,这个首先肯定要引入EntityFramework,然后在model中创建实体类,例如 ...

  3. XJOI1564最小距离问题

    最小距离问题 我国蒙古大草原上有N(N是不大于100的自然数)个牧民定居点P1(X1,Y1).P2(X2,Y2). -Pn(Xn,Yn),相应地有关权重为Wi,现在要求你在大草原上找一点P(Xp,Yp ...

  4. 老李推荐:第14章8节《MonkeyRunner源码剖析》 HierarchyViewer实现原理-获取控件列表并建立控件树 5

    看这段代码之前还是请回到“图13-6-1 NotesList控件列表”中重温一下一个控件的每个属性名和值是怎么组织起来的: android.widget.FrameLayout@41901ab0 dr ...

  5. 性能测试分享:MYSQL死锁

    poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大家咨询qq:908821478,咨询电话010-845052 ...

  6. Xmpp实现简单聊天系列 --- ②用户注册和登陆

    不管是注册还是登陆,亦或是聊天等功能,都必须先进行服务器连接,连接代码如下 ①自定义Xmpp连接工具类:     ②调用创建连接的方法:     ③连接服务器方法:       *注意 连接方式为客服 ...

  7. [转]使用sklearn进行集成学习——实践

    转:http://www.cnblogs.com/jasonfreak/p/5720137.html 目录 1 Random Forest和Gradient Tree Boosting参数详解2 如何 ...

  8. Android 开发之错误整理 [2014-04-28 09:22:28 - XXXX] Unable to resolve target 'android-18'

    在开发的时候难免会导入项目,那么怎么经常会遇到这个错误: [2014-04-28 09:22:28 - XXXX] Unable to resolve target 'android-18' targ ...

  9. .elf格式内容

    arm-linux-ld 可以将程序链接成我们arm平台下的可运行的程序 以之前使用过的led程序为例: 首先: arm-linux-gcc -g -c led.S (-g是表示产生调试信息, -c是 ...

  10. .NET程序员也学Node.js——初识Node.js

    清明在石门休了八天假,一眨眼,4月又到中旬了...看到.NET在天朝彻底沦陷而又无能为力,我开始尝试去学习一些新的东西来充实自己,我自然是打死不会去学java的,没有为什么,于是乎,最近开始学习一些前 ...