小圆前辈的素数(FFT)

该博客介绍了如何利用快速傅里叶变换(FFT)算法来高效解决一个问题:给定两个数组,求所有使得数组元素之和为质数的组合数。博主首先分析了朴素解法的时间复杂度问题,然后提出了使用FFT进行多项式乘法,将时间复杂度降低到O(NlogN),并给出了详细的代码实现。同时,还包含了一个用于判断是否为质数的辅助函数。
摘要由CSDN通过智能技术生成

题目链接:https://ac.nowcoder.com/acm/contest/15332/B

题目大意:

给了两个数组a和b,任意从a中取一个数a[i]和b数组中取一个数b[i],求满足a[i] + b[i]是素数的取法有多少种?

数组长度 <= 1e5.

示例:

a = {1, 2, 2}

b = {3, 4, 6}

满足条件的答案有四种:

{1, 4}, {1, 6}, {2, 3}, {2, 3}

解答:

朴素的两重暴力枚举时间复杂度是O(N^2)无法AC本题.

我们考虑统计a数组中每个数的个数,1有1个,2有两个

$$f(a) = x^1 + 2x^2$$

同理

$$f(b) = x^3 + x^4 + x^6$$

我们计算:

$$ f(a) * f(b) = x^4 + \color{red}{3x^5} + 2x^6 + \color{red}{x^7} + 2x^8 $$

实际上我们需要统计的答案就是上述多项式中x的幂是素数的项的系数

我们知道,FFT可以把多项式乘法做到O(NlgN)的,那么就可以通过本题了。

代码:

#include <bits/stdc++.h>
using namespace std;


//大于n*m的最小的2的幂
const int N = 30'0010;
const double PI = acos(-1);

int n, m;


struct Complex {
    double x, y;
    Complex operator+ (const Complex& t) const {
        return {x + t.x, y + t.y};
    }
    Complex operator- (const Complex& t) const {
        return {x - t.x, y - t.y};
    }
    Complex operator* (const Complex& t) const {
        return {x * t.x - y * t.y, x * t.y + y * t.x};
    }
}a[N], b[N];


int rev[N], bit, tot;

void fft(Complex a[], int inv)
{
    for (int i = 0; i < tot; i ++ )
        if (i < rev[i])
            swap(a[i], a[rev[i]]);
    for (int mid = 1; mid < tot; mid <<= 1)
    {
        auto w1 = Complex({cos(PI / mid), inv * sin(PI / mid)});
        for (int i = 0; i < tot; i += mid * 2)
        {
            auto wk = Complex({1, 0});
            for (int j = 0; j < mid; j ++, wk = wk * w1)
            {
                auto x = a[i + j], y = wk * a[i + j + mid];
                a[i + j] = x + y, a[i + j + mid] = x - y;
            }
        }
    }
}

bool isPrime(int x) {
    for (int i = 2; i * i <= x; i++) if (x % i == 0) return 0;
    return 1;
}


void solve() {
    int _n, _m;
    scanf("%d%d", &_n, &_m);
    n = m = 0;
    for (int i = 0; i < _n; i++) {
        int val;
        scanf("%d", &val);
        a[val].x++;
        n = max(n, val);
    }
    for (int i = 0; i < _m; i++) {
        int val;
        scanf("%d", &val);
        b[val].x++;
        m = max(m, val);
    }
    
    //下面是FFT的板子
    //a[0]*x^0 + a[1]*x^1 + a[2]*x^2 + ... + a[n]*x^n
    // for (int i = 0; i <= n; i ++) scanf("%lf", &a[i].x);
    //b[0]*x^0 + b[1]*x^1 + b[2]*x^2 + ... + b[m]*x^n
    // for (int i = 0; i <= m; i ++) scanf("%lf", &b[i].x);
    bit = tot = 0;
    while ((1 << bit) < n + m + 1) bit++;
    tot = 1 << bit;
    for (int i = 0; i < tot; i ++ )
        rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (bit - 1));
    fft(a, 1), fft(b, 1);
    for (int i = 0; i < tot; i++) a[i] = a[i] * b[i];
    fft(a, -1);
    long long ans = 0;
    for (int i = 2; i <= n + m; i++) {
        // printf("%d ", (int)(a[i].x / tot + 0.5));
        if (isPrime(i)) ans += (long long)(a[i].x / tot + 0.5);
    }
    printf("%lld\n", ans);
    for (int i = 0; i < tot; i++) a[i].x = a[i].y = b[i].x = b[i].y = 0;
        
}

int main() {
    int T;
    scanf("%d", &T);
    while (T--) {
        solve();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值