解题报告(二)C、(darkBZOJ 2194) 快速傅立叶之二(FFT、卷积的概念、常用变换)

繁凡出品的全新系列:解题报告系列 —— 超高质量算法题单,配套我写的超高质量题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数( 1 ∼ 5 1 \sim 5 15),以模板题难度 1 1 1 为基准。


这样大家在学习算法的时候就可以执行这样的流程:

阅读我的【学习笔记】 / 【算法全家桶】学习算法 ⇒ \Rightarrow 阅读我的相应算法的【解题报告】获得高质量题单 ⇒ \Rightarrow 根据我的一句话题解的提示尝试自己解决问题 ⇒ \Rightarrow 点开我的详细题解链接学习巩固(好耶)
%

题单链接:【解题报告】多项式问题(多项式乘法及其各种运算)(ICPC / CCPC / NOIP / CF / AT / NC / P)超高质量题解

C、(BZOJ 2194) 快速傅立叶之二(FFT、卷积的概念、常用变换)

Wbelink

https://darkbzoj.tk/problem/2194

Problem

请计算 C [ k ] = ∑ ( a [ i ] ∗ b [ i − k ] ) \displaystyle C[k]=\sum(a[i]*b[i-k]) C[k]=(a[i]b[ik]) 其中 k < = i < n k < = i < n k<=i<n ,并且有 n < = 1 0 5 n < = 10 ^ 5 n<=105 a , b a,b a,b 中的元素均为小于等于 100 100 100 的非负整数。

n ≤ 1 0 5 n \leq 10^5 n105

Solution

C ( x ) = A ( x ) ∗ B ( x ) = ∑ k = 0 2 n − 2 ( ∑ k = i + j a i b j ) x k C(x)=A(x)* B(x)=\sum_{k=0}^{2n-2}(\sum_{k=i+j}a_ib_j)x^k C(x)=A(x)B(x)=k=02n2(k=i+jaibj)xk

我们知道多项式乘法:
C ( x ) = A ( x ) ∗ B ( x ) = ∑ i = 0 n ( ∑ j = 0 i a j b i − j ) x j + i − j = ∑ i = 0 n ( ∑ j = 0 i a j b i − j ) x i C(x)=A(x)*B(x)=\sum_{i = 0}^{n}(\sum_{j=0}^{i}a_j b_{i-j})x^{j+i-j}=\sum_{i = 0}^{n}(\sum_{j=0}^{i}a_j b_{i-j})x^{i} C(x)=A(x)B(x)=i=0n(j=0iajbij)xj+ij=i=0n(j=0iajbij)xi

也就是对于 C ( x ) C(x) C(x) 的第 k k k 项的系数:

[ k ] C ( x ) = ∑ i = 0 k a i b k − i [k]C(x)=\sum_{i=0}^{k}a_i b_{k-i} [k]C(x)=i=0kaibki

可以理解为只有小于 k k k 的系数才有可能给 x k x^k xk 做贡献,即 x j × x k − j = x k x^j\times x^{k-j}=x^k xj×xkj=xk。而大的次幂乘起来就大于 k k k 了。

而本题中要求的式子实际上可以写成:

[ k ] C ( x ) = ∑ i = k n − 1 a i b i − k [k]C(x)=\sum_{i=k}^{n-1}a_ib_{i-k} [k]C(x)=i=kn1aibik

发现本题中要求的式子和标准的多项式卷积不太一样,所以考虑如何转化一下。

很明显这两个式子的唯一差别就是卷积循环的是前半部分,题中所求的式子循环的是后半部分,所以我们可以想办法把他们交换一下:我们设 a ′ a' a a a a 数组的翻转,即: a [ i ] = a ′ [ n − 1 − i ] a[i] = a'[n - 1-i] a[i]=a[n1i] a ′ [ i ] = a [ n − 1 − i ] a'[i] = a[n - 1-i] a[i]=a[n1i] ,二者等价。 (减一是因为 a a a 数组是从 0 ∼ n − 1 0\sim n-1 0n1)。

故原式等于:

[ k ] D ( x ) = ∑ i = k n − 1 a n − 1 − i ′ b i − k = [ n − 1 − k ] C ( x ) [k]D(x)=\sum_{i=k}^{n-1}a'_{n-1-i}b_{i-k}=[n-1-k]C(x) [k]D(x)=i=kn1an1ibik=[n1k]C(x)

因为: i : k ∼ n − 1 i:k\sim n-1 i:kn1

j = n − 1 − i : n − 1 − k ∼ n − 1 − ( n − 1 ) ⇒ 0 ∼ n − 1 − k j=n-1-i:n-1-k\sim n-1-(n-1) \Rightarrow 0\sim n-1-k j=n1i:n1kn1(n1)0n1k

[ k ] D ( x ) = ∑ j = n − 1 − i = 0 n − 1 − k a n − 1 − i ′ b i − k [k]D(x)=\sum_{j=n-1-i=0}^{n-1-k}a'_{n-1-i}b_{i-k} [k]D(x)=j=n1i=0n1kan1ibik

n − 1 − i + i − k = n − 1 − k n-1-i+i-k=n-1-k n1i+ik=n1k 是一个常数。

这样就变成了一个标准的卷积的形式,即可使用 FFT 求解。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
#include <queue>
using namespace std;
typedef long long ll;
typedef int itn;
typedef pair<int, int>PII;
const int N = 2e5 + 7, mod = 1e9 + 7;

const double PI = acos(-1.0);

int n, m, RR[N], limit, L;

struct Complex
{
    double x, y;
    Complex (double x = 0, double y = 0) : x(x), y(y) { }
    Complex operator + (const Complex &t)
    {
        return Complex (x + t.x, y + t.y);
    }
    Complex operator - (const Complex &t)
    {
        return Complex (x - t.x, y - t.y);
    }
    Complex operator * (const Complex &t)
    {
        return Complex (x * t.x - y * t.y, x * t.y + y * t.x);
    }
}A[N], B[N], C[N];


void FFT(Complex *A, double type)
{
    for(int i = 0; i < limit; ++ i)
        if(i < RR[i])
            swap(A[i], A[RR[i]]);

    for(int mid = 1; mid < limit; mid <<= 1) {
        Complex wn(cos(PI / mid), type * sin(PI / mid));

        for(int len = mid << 1, pos = 0; pos < limit; pos += len) {
            Complex w(1, 0);
            for(int k = 0; k < mid; ++ k, w = w * wn) {
                Complex x = A[pos + k];
                Complex y = w * A[pos + mid + k];
                A[pos + k] = x + y;
                A[pos + mid + k] = x - y;
            }
        }
    }
    if(type == -1) {
        for(int i = 0; i <= limit; ++ i)
            A[i].x /= limit;
    }
}

//a'[i] = a[n - 1 - i];

int main()
{
    scanf("%d", &n);
    for(int i = 0; i < n; ++ i) {
        int x, y;
        scanf("%d%d", &x, &y);
        A[n - 1 - i].x = x;
        B[i].x = y;
    }
    L = 0, limit = 1;
    while(limit <= n * 2) limit <<= 1, L ++ ;
    for(int i = 0; i < limit; ++ i) {
        RR[i] = (RR[i >> 1] >> 1) | ((i & 1) << (L - 1));
    }

    FFT(A, 1), FFT(B, 1);
    for(int i = 0; i < limit; ++ i) {
        C[i] = A[i] * B[i];
    }
    FFT(C, -1);
    for(int i = n - 1; i >= 0; -- i) {
        printf("%d\n", (int)(C[i].x + 0.5));
    }
    return 0;
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁凡さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值