POJ3150【FFT】

转移很好用矩阵表示.然而矩阵乘法复杂度是O(n^3)的.

很容易发现转移矩阵是【循环矩阵】.而且有一个美妙的性质:【循环矩阵 * 循环矩阵 = 循环矩阵】.

所以我们计算矩阵乘法的时候可以只计算第一行.剩下的可以由第一行递推得出.

一次乘法的复杂度降到了O(n^2).这是可以接受的.

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <string>
#define make make_pair
#define fi first
#define se second  

using namespace std;  

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;  

const int maxn = 1000010;
const int maxm = 1010;
const int maxs = 30;
const int inf = 0x3f3f3f3f;
const int P = 1000000007;
const double error = 1e-9;
const double Pi = 3.141592653589793238462643383279;

inline int read()
{
	int x = 0, f = 1; char ch = getchar();
	while (ch <= 47 || ch >= 58)
		f = (ch == 45 ? -1 : 1), ch = getchar();
	while (ch >= 48 && ch <= 57)
		x = x * 10 + ch - 48, ch = getchar();
	return x * f;
}

struct matrix
{
	ll num[501][501];
} f, e;

int n, m, d, k; ll a[501], b[501];

matrix operator * (matrix a, matrix b)
{
	matrix c;
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		c.num[i][j] = 0;
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		(c.num[1][i] += a.num[1][j] * b.num[j][i]) %= m;
	for (int i = 2; i <= n; i++)
	for (int j = 1; j <= n; j++)
		c.num[i][j] = c.num[i - 1][j == 1 ? n : j - 1];
	return c;
}	

void init()
{
	for (int i = 1; i <= n; i++)
		e.num[i][i] = 1, a[i] = read();
	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++) {
		int a = min(i, j), b = max(i, j);
		int dis = min(b - a, n + a - b);
		if (dis <= d) f.num[i][j] = 1;
	}
}

int main()
{
	n = read(), m = read();
	d = read(), k = read();

	init();

	for (int i = k; i != 0; f = f * f, i /= 2)
		if (i & 1) e = e * f;

	for (int i = 1; i <= n; i++)
	for (int j = 1; j <= n; j++)
		(b[i] += a[j] * e.num[j][i]) %= m;

	for (int i = 1; i <= n; i++)
		printf("%lld%c", b[i], i == n ? 10 : 32);

	return 0;

我们继续考虑.既然循环矩阵可以由第一行递推得出.那么我们可以只保存矩阵的第一行和第一列.

计算乘法的时候.发现很容易变成卷积形式.那么就可以用FFT优化了.

在本题中.转移阵不仅是【循环矩阵】.而且关于主对角线对称.因此第一行和第一列相同.只用保存一个.

这道题用FFT可以做到O(n*logn*logk).在POJ上排名rank1.Excited!

/* I will wait for you */

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <ctime>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <vector>
#include <queue>
#include <deque>
#include <set>
#include <map>
#include <string>
#define make make_pair
#define fi first
#define se second

using namespace std;  

typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

const int maxn = 1000010;
const int maxm = 501;
const int maxs = 30;
const int inf = 0x3f3f3f3f;
const int P = 1000000007;
const double error = 1e-9;
const double Pi = 3.141592653589793238462643383279;

inline int read()
{
	int x = 0, f = 1; char ch = getchar();
	while (ch <= 47 || ch >= 58)
		f = (ch == 45 ? -1 : 1), ch = getchar();
	while (ch >= 48 && ch <= 57)
		x = x * 10 + ch - 48, ch = getchar();
	return x * f;
}

struct complex
{
	double re, im;
} _x[maxn], _y[maxn], w[2][maxn];

complex operator + (complex a, complex b)
{
	complex c;
	c.re = a.re + b.re;
	c.im = a.im + b.im;
	return c;
}

complex operator - (complex a, complex b)
{
	complex c;
	c.re = a.re - b.re;
	c.im = a.im - b.im;
	return c;
}

complex operator * (complex a, complex b)
{
	complex c;
	c.re = a.re * b.re - a.im * b.im;
	c.im = a.im * b.re + a.re * b.im;
	return c;
}

struct matrix
{
	ll num[maxm];
} a, f;

int n, m, d, k, _n = 1, rev[maxn];

void _init()
{
	for (int i = 0; i < _n; i++)
	for (int j = i, k = 1; k < _n; k <<= 1, j >>= 1)
		(rev[i] <<= 1) |= (j & 1);
	for (int i = 0; i < _n; i++) {
		w[0][i].re = cos(2 * Pi * i / _n);
		w[0][i].im = sin(2 * Pi * i / _n);
		w[1][i].re = cos(2 * Pi * i / _n);
		w[1][i].im = -sin(2 * Pi * i / _n);
	}
}

void FFT(complex *a, int f)
{
	for (int i = 0; i < _n; i++)
		if (rev[i] > i) swap(a[i], a[rev[i]]);
	for (int i = 1; i < _n; i <<= 1)
	for (int j = 0, l = _n / (i << 1); j < _n; j += (i << 1))
	for (int k = 0, t = 0; k < i; k += 1, t += l) {
		complex x = a[j + k], y = w[f][t] * a[i + j + k];
		a[j + k] = x + y, a[i + j + k] = x - y;
	}
	for (int i = 0; f && i < _n; i++) a[i].re /= _n;
}

matrix operator * (matrix a, matrix b)
{
	matrix c;
	for (int i = 0; i < _n; i++) {
		_x[i].re = _x[i].im = 0;
		_y[i].re = _y[i].im = 0;
	}
	for (int i = 0; i < n; i++) {
		_x[i].re = a.num[n - i];
		_y[i].re = b.num[i + 1];
		_y[n + i].re = _y[i].re;
	}
	FFT(_x, 0), FFT(_y, 0);
	for (int i = 0; i < _n; i++)
		_x[i] = _x[i] * _y[i];
	FFT(_x, 1);
	for (int i = 1; i <= n; i++)
		c.num[i] = (ll) (_x[2 * n - i].re + 0.5) % m;
	return c;
}

void init()
{
	for (int i = 1; i <= n; i++)
		a.num[i] = read();
	for (int i = 1; i <= n; i++) {
		int dis = min(i - 1, n + 1 - i);
		f.num[i] = (dis <= d);
	}
	while (_n < n << 2) _n <<= 1;
}

int main()
{
	n = read(), m = read();
	d = read(), k = read();
	init(), _init();

	for (int i = k; i != 0; i /= 2, f = f * f)
		if (i & 1) a = a * f;
	for (int i = 1; i <= n; i++)
		printf("%lld ", (ll) (a.num[i] + 0.5));

	return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-17 19:18:55

POJ3150【FFT】的相关文章

【FFT】专题总结

学了若干天终于学(bei)会了传说中的法法塔 感觉也没那么难用嘛 fft快速傅里叶变换 在大表课件上写就是解决高精乘的工具 其实很有理有据 fft就是用复数的折半引理优化两个多项式相乘的高端东西 他能使O(n^2)的多项式相乘优化到O(nlogn) 听ak说这也是比较模板的东西 也就不去理解什么证明了(其实是我看了半天看不懂TAT) 贴个代码吧(史上最短总结233- -) 1 int bit_rev(int t,int n){ 2 int res=0; 3 for (int i=0;i<n;i+

【FFT】快速傅里叶变换

[FFT]快速傅里叶变换 一.复数 1.定义 复数:设 $a$,$b$ 为实数,$i^{2}=−1$ ,形如 $a+bi$ 的数叫复数,其中 $i$ 被称为虚数单位,复数域是目前已知最大的域 在复平面中,$x$ 代表实数,$y$ 轴(除原点外的点)代表虚数,从原点 $(0,0)$ 到 $(a,b)$ 的向量表示复数 $a+bi$ 模长:从原点 $(0,0)$ 到点 $(a,b)$ 的距离,即 $\sqrt{a^2+b^2}$ 幅角:假设以逆时针为正方向,从 $x$ 轴正半轴到已知向量的转角的有向

【HDU1402】【FFT】A * B Problem Plus

Problem Description Calculate A * B. Input Each line will contain two integers A and B. Process to end of file. Note: the length of each integer will not exceed 50000. Output For each case, output A * B in one line. Sample Input 1 2 1000 2 Sample Out

【清橙A1084】【FFT】快速傅里叶变换

问题描述 离散傅立叶变换在信号处理中扮演者重要的角色.利用傅立叶变换,可以实现信号在时域和频域之间的转换. 对于一个给定的长度为n=2m (m为整数) 的复数序列X0, X1, …, Xn-1,离散傅立叶变换将得到另一个长度为n的复数序列Y0, Y1, …, Yn-1.其中 Yi=X0+X1wi+ X2w2i+ X3w3i+…+ Xn-1w(n-1)i 其中w=e2πI/n=cos(2π/n)+I sin(2π/n),称为旋转因子,其中I为虚数单位,I2= –1. 给定输入序列X,请输出傅立叶变

【FFT】BZOJ2179- FFT快速傅立叶

[题目大意] 给出n位十进制a和b,求a*b. [思路] FFT.感觉弄起来比较麻烦,不如直接背板子. 注意一下MAXN的取值,我一开始非常随意地就写了60000*2+50,其实n是要扩展到最接近的2的次幂的,所以要取到2^17 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<complex> 6 #inclu

【BZOJ3527】【FFT】力

[问题描述]给出n个数qi,给出Fj的定义如下:令Ei=Fi/qi.试求Ei.[输入格式]输入文件force.in包含一个整数n,接下来n行每行输入一个数,第i行表示qi.[输出格式]输出文件force.out有n行,第i行输出Ei.与标准答案误差不超过1e-2即可.[样例输入]54006373.88518415375036.4357591717456.4691448514941.0049121410681.345880[样例输出]-16838672.6933439.7937509018.566

BZOJ3527 [Zjoi2014]力 【fft】

题目 给出n个数qi,给出Fj的定义如下: 令Ei=Fi/qi,求Ei. 输入格式 第一行一个整数n. 接下来n行每行输入一个数,第i行表示qi. 输出格式 n行,第i行输出Ei.与标准答案误差不超过1e-2即可. 输入样例 5 4006373.885184 15375036.435759 1717456.469144 8514941.004912 1410681.345880 输出样例 -16838672.693 3439.793 7509018.566 4595686.886 1090304

【点分治】【FFT】Gym - 101234D - Forest Game

存个求树上每种长度(长度定义为路径上点数)的路径条数的模板:num数组中除了长度为1的以外,都算了2次. 不造为啥FFT数组要开八倍. #include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> using namespace std; typedef long long ll; const ll MOD=1000000007l

【FFT】OpenJ_POJ - C17H - Reverse K-th Problem

对每个位置i处理出以其为结尾,且比a(i)大的数有j个的前缀个数,记成一个数组l:同理,处理出以其为开头,且比a(i)大的数有j个的后缀的个数,记成一个数组r. 整个序列中比a(i)大的数的个数的数组就是对l和r数组卷积起来. 于是枚举所有i,FFT,累加答案即可. 但是,有可能有重复的元素,就将a(i)前面的和它相同的数当成比它大,后面的和它相同的数当成比他小即可. 存疑:FFT的数组到底要开多大啊?四倍?还是只要是一个比卷积结果数组长度大的2的整数幂次就行了?我倾向于后者.求解. 2#inc