在做题打比赛的时候,FFT(快速傅里叶变换)这个名词超级常见,这个算法实际上是在结合复数,多项式的点值表示等知识,加速两个多项式的乘法运算的过程
HDU 1402
两个高精大数的乘法可以看成两个多项式的乘积,所以可以用FFT来优化
FFT的实际流程为: 将原多项式转化到复数空间,此空间内多项式可以直接一对一乘。乘完后再逆转化回到实数空间。此时所有的数取整就得到了最后的ans
FFT有几个需要注意的问题:
- Len是两个多项式卷积后的最终多项式长度,需要为2的k次方
- 你需要凭借这个Len初始化复数数组:
x1[0~len-1]={a[i],0} x2[0~len-1]={b[i],0}
- 如果两个数组是同一个,只需要进行一次DFT
- 数组大小开原数组最大长度
x
的4倍:因为len>2*x,且len为2^k
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <math.h>
using namespace std;
const double PI = acos(-1.0);
//复数结构体
struct Complex {
double x, y; //实部和虚部 x+yi
Complex(double _x = 0.0, double _y = 0.0) {
x = _x;
y = _y;
}
Complex operator -(const Complex &b) const {
return Complex(x - b.x, y - b.y);
}
Complex operator +(const Complex &b) const {
return Complex(x + b.x, y + b.y);
}
Complex operator *(const Complex &b) const {
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
};
/*
* 进行FFT和IFFT前的反转变换。
* 位置i和 (i二进制反转后位置)互换
* len必须去2的幂
*/
void change(Complex y[], int len) {
int i, j, k;
for (i = 1, j = len / 2; i < len - 1; i++) {
if (i < j)
swap(y[i], y[j]);
//交换互为小标反转的元素,i<j保证交换一次
//i做正常的+1,j左反转类型的+1,始终保持i和j是反转的
k = len / 2;
while (j >= k) {
j -= k;
k /= 2;
}
if (j < k)
j += k;
}
}
/*
* 做FFT
* len必须为2^k形式,
* on==1时是DFT,on==-1时是IDFT
*/
void fft(Complex y[], int len, int on) {
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].x /= len;
}
const int MAXN = 200010;
Complex x1[MAXN], x2[MAXN];
char str1[MAXN / 2], str2[MAXN / 2];
int sum[MAXN];
int main() {
while (scanf("%s%s", str1, str2) == 2) {
int len1 = strlen(str1);
int len2 = strlen(str2);
int len = 1;
while (len < len1 * 2 || len < len2 * 2)
len <<= 1;
for (int i = 0; i < len1; i++)
x1[i] = Complex(str1[len1 - 1 - i] - '0', 0);
for (int i = len1; i < len; i++)
x1[i] = Complex(0, 0);
for (int i = 0; i < len2; i++)
x2[i] = Complex(str2[len2 - 1 - i] - '0', 0);
for (int i = len2; i < len; i++)
x2[i] = Complex(0, 0);
//求DFT
fft(x1, len, 1);
fft(x2, len, 1);
for (int i = 0; i < len; i++)
x1[i] = x1[i] * x2[i];
fft(x1, len, -1);
for (int i = 0; i < len; i++)
sum[i] = (int) (x1[i].x + 0.5);
for (int i = 0; i < len; i++) {
sum[i + 1] += sum[i] / 10;
sum[i] %= 10;
}
len = len1 + len2 - 1;
while (sum[len] <= 0 && len > 0)
len--;
for (int i = len; i >= 0; i--)
printf("%c", sum[i] + '0');
printf("\n");
}
return 0;
}
   \\\;
   \\\;
HDU 4609
题意: n个木棍,问取三个组成三角形的概率
解析:
卷积:向量{ a 1 , a 2 , a 3 a_1,a_2,a_3 a1,a2,a3}的卷积相当于多项式的乘
首先处理出sum[i]
,表示两根加起来为i
的方案数:
假设原数组为:1,2,3,3,4,我们处理一个累加数组d[i]
表示长度为i
的有多少根,d数组为:{1,1,2,1}
显然,d数组的卷积就是sum数组(
s
u
m
[
k
]
=
∑
i
=
1
k
−
1
d
[
i
]
∗
d
[
k
−
i
]
sum[k]=\sum_{i=1}^{k-1}d[i]*d[k-i]
sum[k]=∑i=1k−1d[i]∗d[k−i])
但是这里需要容斥两部分:取两次自己2*a=a+a
,被算两次a+b=b+a
,所以FFT得出的sum需要sum[2*a[i]]--,sum[j]/=2
我开始以为虽然有的会减1再除2,和直接除2一样(偶数+1),后来WA了,因为有些是(奇数+1)
注意数组大小一定要27000
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const double PI = acos(-1.0);
//复数结构体
struct Complex {
double x, y; //实部和虚部 x+yi
Complex(double _x = 0.0, double _y = 0.0) {
x = _x;
y = _y;
}
Complex operator -(const Complex &b) const {
return Complex(x - b.x, y - b.y);
}
Complex operator +(const Complex &b) const {
return Complex(x + b.x, y + b.y);
}
Complex operator *(const Complex &b) const {
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
};
/*
* 进行FFT和IFFT前的反转变换。
* 位置i和 (i二进制反转后位置)互换
* len必须去2的幂
*/
void change(Complex y[], int len) {
int i, j, k;
for (i = 1, j = len / 2; i < len - 1; i++) {
if (i < j)
swap(y[i], y[j]);
//交换互为小标反转的元素,i<j保证交换一次
//i做正常的+1,j左反转类型的+1,始终保持i和j是反转的
k = len / 2;
while (j >= k) {
j -= k;
k /= 2;
}
if (j < k)
j += k;
}
}
/*
* 做FFT
* len必须为2^k形式,
* on==1时是DFT,on==-1时是IDFT
*/
void fft(Complex y[], int len, int on) {
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].x /= len;
}
const int MAXN = 400010;
Complex x1[MAXN], x2[MAXN];
LL x[MAXN];
LL d[MAXN];
LL sum[MAXN];
int main() {
int t;scanf("%d",&t);
while (t--) {
memset(d,0,sizeof(d));
LL n;scanf("%lld",&n);
for(int i=0;i<n;i++){
scanf("%lld",&x[i]);d[x[i]]++;
}
sort(x,x+n);
int len=1;
while(len<2*(x[n-1]+1))len<<=1;
for (int i = 0; i < len; i++)
x1[i] = Complex(d[i], 0);
//x2[i] = Complex(d[i], 0);因为自己和自己卷积,所以只需要做一次即可
//求DFT
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++)
sum[i] = (LL) (x1[i].x + 0.5);
for(int i=0;i<n;i++){
sum[x[i]*2]--;
}
for(int i=0;i<len;i++){
sum[i] /= 2;
}
LL k=0;
LL all=n*(n-1ll)*(n-2ll)/6ll;
LL ans=all;
for(int i=0;i<len;i++){
if(sum[i]==0)continue;
while(x[k]<i&&k<n)k++;
if(k==n)break;
ans-=sum[i]*(n-k);
}
cout<< setiosflags(ios::fixed)<<setprecision(7) <<1.0*ans/(1.0*all)<<endl;
}
return 0;
}