快乐暑假(十)——关于FFT算法中倒置二进制算法的一点补充

快速傅里叶变换经过了使用雷德算法进行迭代优化,在效率上已经有了很大的进步。
但是,如果一位一位翻转,尽管位数不是很多,数据个数非常多的时候还是比较慢。所以,我们用一个类似DP的方法来实现这个功能。

目前还不知道这个算法的名字


 void get_rev(int bit)//bit表示二进制的位数
{  
    for(int i=0;i<(1<<bit);i++)1~2^bit-1中的所有数做长度为bit的二进制翻转
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
}

这个代码一看除了知道使用很多位运算完全不知道在干什么。
我们可以把一个二进制看成两部分:

  • 它的前bit-1是一部分
  • 最后一位是一部分

在雷德算法中提到过,二进制翻转就是把它的最后一位当成首位。我们仔细考虑这个问题,其实可以发现这个问题其实有一个最优子结构。

每一个二进制翻转就相当于把他的最后一位当成首位,然后在前面接上它的前bit-1位的进制翻转。

下面的问题就转换为,如何找到一个数的前bit-1位的进制转换是多少。
其实也很好找,“i”的前bit-1位的数值其实就是 ⌊ i 2 ⌋ {\lfloor\frac{i}{2}\rfloor} 2i(向下取整)的值,也就是i右移一位的的值。

举个例子:
5: 101
它的前2位为:10
5右移一位就是:10
在这里插入图片描述

但是,实际上,i>>1的翻转与“i”的前bit-1位的翻转是有一些不同的,因为我们的二进制翻转始终以bit位为标准,所以i>>1会比“i”的前bit-1位多出一个前导零,而翻转之后就会多出一个“后缀零”,所以“i”的前bit-1位的翻转要去掉那个“后缀零”,也就是“rev[i>>1]>>1”。

还是以5举例,5>>1 = 2 (010),但5的前两位是10,多了一个前置的‘0’,所以我们翻转之后这个前置的‘0’就到了最后,所以我们再次右移把后边的0去掉。

在这里插入图片描述

我们只要把末尾乘上2^(bit-1)变成首位,再加上rev[i>>1]>>1就是我们要的答案了。

原本的代码应该是这样

rev[i]=(rev[i>>1]>>1)+((i&1)<<(bit-1));

但是代码里用了按位或‘ | ’来加快速度。反正都用了那么多位运算,就不在乎这一个两个了

因为(i&1)^(bit-1)的后bit-1位都是零,0|1=1 0|0=0,0+1=1 0+0=0。此时“按位或”与加法等价。

完整FFT

#include <cstdio>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

const double PI = acos(-1);
const int SZ = 1e4 + 7;
int rev[SZ];
template <typename T> 
struct complex {
	T r, m;
	complex(T _r=0, T _m=0): r(_r), m(_m) {}
	complex operator + (const complex &b) {
		return complex(r+b.r, m+b.m);
	}
	complex operator - (const complex &b) {
		return complex(r-b.r, m-b.m);
	}
	//(a+ib)*(c+id)=ac-bd + i(ad+bc)
	complex operator * (const complex &b) {
		return complex(r*b.r-m*b.m, r*b.m+m*b.r);
	}
};
complex<double> a[SZ], b[SZ], c[SZ<<1];

void get_rev(complex<double> a[],int n)//bit表示二进制的位数
{
	memset(rev,0,sizeof(rev));
	int bit=0;
    while ((1<<bit)<n) bit++;
    for(int i = 0;i < n; i++){//对1~2^bit-1中的所有数做长度为bit的二进制翻转
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
		if (i<rev[i]) std::swap(a[i],a[rev[i]]);//不加这条if会交换两次(就是没交换)
	}
}

//op为1是正变换,-1是逆变换 
void fft(complex<double> a[], int n, int op) {
	get_rev(a,n);
	for (int i = 2; i <= n; i <<= 1) {
		complex<double> wn(cos(2*PI*op/i), sin(2*PI*op/i));
		for (int j = 0; j < n; j += i) {
			complex<double> w(1, 0);
			for (int k = j, end = j+i/2; k < end; ++k) {
				complex<double> t1 = a[k], t2 = w * a[k+i/2];
				a[k] = t1 + t2, a[k+i/2] = t1 - t2;
				w = w * wn;
			}
		}
	}
	if (op == -1) for (int i = 0; i < n; ++i) a[i].r /= n;
}
int main()
{
	int n, m;
	//输入多项式的最高幂,以及系数 
	scanf("%d%d", &n, &m);
	for (int i = 0; i <= n; i++) scanf("%lf", &a[i]);
	for (int i = 0; i <= m; i++) scanf("%lf", &b[i]);
	m += n, n = 1; 
	//寻找合适的2的幂,赋值为n 
	while (n < m) n <<= 1;
	//正变换 
	fft(a, n, 1), fft(b, n, 1);
	//0~n-1,n为2的整数幂 
	for (int i = 0; i < n; i++) c[i] = a[i] * b[i];
	//逆变换 
	fft (c, n, -1);
	for (int i = 0; i <= m; i++) printf("%.0f ", c[i].r);
	return 0;
}
/*
3 2
1 1 2 3
1 2 3
1 3 7 10 12 9
*/

博客参考:https://blog.csdn.net/GGN_2015/article/details/69518685

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值