快速傅立叶 哈尔滨理工ACM程序设计全国邀请赛(网络同步赛) D. Pairs

本文介绍了一种使用快速傅立叶变换(FFT)优化求解特定问题的方法,该问题要求在给定一组整数的情况下计算小于某个阈值的所有数对之和的数量。文章详细解释了如何通过将问题转化为FFT中的卷积运算来实现高效的求解过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Pairs

Description

Given N integers,count the number of pairs of integers whose sum is less than K. And we have Mqueries. 

Input

The first behavioris an integer T (1 < = T < = 10), on behalf of the number of sets ofdata. Each group of data is the first line N, M (1 < = n, m < = 100000),The next line will give N integers. a[i] (0<=a[i]<=100000).

The next M lines,each line contains the query K (1 <= K<=200000).

Output

For each query,output the number of pairs of integers whose sum is less than K.

Sample Input

1

5 2

1 5 3 4 2

5

7

Sample Output

2

这题确实很巧妙,开始以为能hash一下,但仔细分析后感觉还是会超时,一直没有什么好办法做,看了题解之后,居然是用fft(快速傅立叶变换)做的,花了一下午+一晚上,还没把傅立叶看懂,但是勉强把fft的模板看懂了,大体上明白这个题的思路,fft的作用无非就是求个卷积,这个卷积取余进位就是nlogn的大数乘法,本质上就是求两个序列


积的系数,大数的乘法也是上面这个表达式的特殊情况,xi表示位数

回到这个题,hash的思想其实没有问题,先把任意两个数的和存在一个表里,然后前缀和, q*O(1)的时间复杂度

但是hash的预处理却要O(N*N) ,这里就要用fft进行优化,

预处理的时候我们是把每一个数,与其他所有的数加了一次 , 这里把一个集合考虑成完全相同的两个,不就等价于从一个集合选一个元素,与另外一个集合所有元素相加一次

类比大数的乘法, 12345*78912    计算这个的时候不就是把下面每一个数乘了上面一次吗?

所以大数的乘法是怎么优化的,这个题就是怎么优化的,

只不过要先对输入的数据做一些处理,

for (int i = 1;i <= n;++i ){
    scanf("%d",&nub[i]);
    x1[nub[i]]++;
}


要把数据转化为hash的形式,把两个相同的x1序列作为参数进行快速傅立叶变换后得到的sum

solve(x1, l+1, x1, l+1, sum);

sum[i] 的意思是其实就是两个数相加等于i的个数 , 只不过里面有重复

第一个重复是在把一个集合考虑成两个集合的时候,可以同时选取了同一个元素x, 这个时候要在sum中减去这个数的两倍

即 sum[x+x] --;

第二个重复是这里考虑了顺序,但其实选取的两个元素本身没有顺序,所以除2即可

最后用前缀和处理一下就行了

注意sum序列边界的选取

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <map>
#include <queue>
#include <set>
#include <vector>
using namespace std;
#define L(x) (1 << (x))
const double PI = acos(-1.0);
const int Maxn = 133015;
double ax[Maxn], ay[Maxn], bx[Maxn], by[Maxn];
char sa[Maxn/2],sb[Maxn/2];
int sum[Maxn];
int x1[Maxn],x2[Maxn];
int revv(int x, int bits)
{
    int ret = 0;
    for (int i = 0; i < bits; i++)
    {
        ret <<= 1;
        ret |= x & 1;
        x >>= 1;
    }
    return ret;
}
void fft(double * a, double * b, int n, bool rev)
{
    int bits = 0;
    while (1 << bits < n) ++bits;
    for (int i = 0; i < n; i++)
    {
        int j = revv(i, bits);
        if (i < j)
            swap(a[i], a[j]), swap(b[i], b[j]);
    }
    for (int len = 2; len <= n; len <<= 1)
    {
        int half = len >> 1;
        double wmx = cos(2 * PI / len), wmy = sin(2 * PI / len);
        if (rev) wmy = -wmy;
        for (int i = 0; i < n; i += len)
        {
            double wx = 1, wy = 0;
            for (int j = 0; j < half; j++)
            {
                double cx = a[i + j], cy = b[i + j];
                double dx = a[i + j + half], dy = b[i + j + half];
                double ex = dx * wx - dy * wy, ey = dx * wy + dy * wx;
                a[i + j] = cx + ex, b[i + j] = cy + ey;
                a[i + j + half] = cx - ex, b[i + j + half] = cy - ey;
                double wnx = wx * wmx - wy * wmy, wny = wx * wmy + wy * wmx;
                wx = wnx, wy = wny;
            }
        }
    }
    if (rev)
    {
        for (int i = 0; i < n; i++)
            a[i] /= n, b[i] /= n;
    }
}
int solve(int a[],int na,int b[],int nb,int ans[])
{
    int len = max(na, nb), ln;
    for(ln=0; L(ln)<len; ++ln);
    len=L(++ln);
    for (int i = 0; i < len ; ++i)
    {
        if (i >= na) ax[i] = 0, ay[i] =0;
        else ax[i] = a[i], ay[i] = 0;
    }
    fft(ax, ay, len, 0);
    for (int i = 0; i < len; ++i)
    {
        if (i >= nb) bx[i] = 0, by[i] = 0;
        else bx[i] = b[i], by[i] = 0;
    }
    fft(bx, by, len, 0);
    for (int i = 0; i < len; ++i)
    {
        double cx = ax[i] * bx[i] - ay[i] * by[i];
        double cy = ax[i] * by[i] + ay[i] * bx[i];
        ax[i] = cx, ay[i] = cy;
    }
    fft(ax, ay, len, 1);
    for (int i = 0; i < len; ++i)
        ans[i] = (int)(ax[i] + 0.5);
    return len;
}
const int SIZE  =  1000000;
int nub[SIZE];
int ans[SIZE];
int main()
{
    int t;
    int n,q,c,l;
    scanf("%d",&t);
    while( t-- )
    {
        memset(sum, 0, sizeof(sum));
        scanf("%d%d",&n,&q);
        l = 0;
        for (int i = 1;i <= n;++i ){
            scanf("%d",&nub[i]);
            x1[nub[i]]++;
            l = max(l ,nub[i]);
        }
        solve(x1, l+1, x1, l+1, sum);
        l += l;
        //for (int i = 1;i <= l;++i)cout <<sum[i]<<" ";
        for ( int i = 1;i <= n;++i )
            sum[ nub[i]*2 ] --;

        ans[0] = 0;
        for (int i = 1;i <= l;++i){
            sum[i] >>= 1;
            ans[i] = sum[i] + ans[i-1];
        }
        while ( q-- ){
            scanf("%d",&c);
            printf("%d\n",ans[c-1]);
        }
    }
    return 0;
}






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值