快速傅里叶变换 FFT

在做题打比赛的时候,FFT(快速傅里叶变换)这个名词超级常见,这个算法实际上是在结合复数,多项式的点值表示等知识,加速两个多项式的乘法运算的过程

HDU 1402

两个高精大数的乘法可以看成两个多项式的乘积,所以可以用FFT来优化

FFT的实际流程为: 将原多项式转化到复数空间,此空间内多项式可以直接一对一乘。乘完后再逆转化回到实数空间。此时所有的数取整就得到了最后的ans

FFT有几个需要注意的问题:

  1. Len是两个多项式卷积后的最终多项式长度,需要为2的k次方
  2. 你需要凭借这个Len初始化复数数组:x1[0~len-1]={a[i],0} x2[0~len-1]={b[i],0}
  3. 如果两个数组是同一个,只需要进行一次DFT
  4. 数组大小开原数组最大长度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;
}

&ThickSpace; \\\;


&ThickSpace; \\\;

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=1k1d[i]d[ki]

但是这里需要容斥两部分:取两次自己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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值