[2016ACM多校] HDU5730 FFT 重叠相加法

本文介绍了一种利用CDQ分治解决特定卷积计算问题的方法,通过重叠相加法和快速傅里叶变换(FFT)实现高效计算。针对给定问题,文章详细解释了如何使用CDQ分治思想动态调整输入序列长度,最终达到O(NlogN)的时间复杂度。

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

题意

x(n)=x(n)h(n)[u(n)-u(n-N-1)],求x(N)。N为10^5,表示卷积。

思路

不要一看这个式子就以为h(n)是delta(n),如果h(n)=delta(n)则满足这个式子,但不能逆向推导。这里h(0)=0,也就是说每个x(n)是由前面的结果得出的。不能直接快速傅里叶变换求卷积。
既然x(n)是强制在线一个一个输入的,显然是数字信号处理中的分段流水处理,有重叠相加法和重叠保留法。但是输入段的有效长度为1,肯定会超时,重叠保留法在此题就没有优化空间了。
考虑重叠相加法,如果加长每次运算的长度,那么输出就是多节运算的叠加。考虑如何能减少叠加的次数。当然接着fft的分治思路想到cdq分治,不固定输入长度,而用二分法动态决定输入序列的长度,树状划分,重叠相加,就可以在O(NlogN)的时间解决了。

AC代码 C++

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>

using namespace std;

#define MAXN (100005 << 1)
#define MOD 313

namespace FFT
{
    const int FFT_N = MAXN;
    const int FFT_MOD = MOD;
    const double pi = acos(-1.0);
    int pos[FFT_N];
    struct comp{
        double r,i;
        comp(double _r = 0, double _i =0): r(_r), i(_i){}
        comp operator + (const comp& x){return comp(r + x.r, i + x.i);}
        comp operator - (const comp& x){return comp(r - x.r, i - x.i);}
        comp operator * (const comp& x){return comp(r * x.r - i * x.i, i * x.r + r * x.i);}
        comp conj(){return comp(r, -i);}
    }A[FFT_N], B[FFT_N];
    void FFT(comp a[], int n , int t)
    {
        for(int i=1; i<n; i++)
            if(pos[i] > i)
                swap(a[i], a[pos[i]]);
        for(int d = 0;(1<<d) < n ;d++)
        {
            int m = 1<<d, m2 = m<<1;
            double o = pi*2 / m2 * t;
            comp _w(cos(o), sin(o));
            for(int i=0;i<n;i+=m2)
            {
                comp w(1,0);
                for(int j=0;j<m;j++)
                {
                    comp& A = a[i + j + m], &B = a[i+j], t = w*A;
                    A = B - t;
                    B = B + t;
                    w = w * _w;
                }
            }
        }
        if(t == -1)
            for(int i=0; i<n; i++)
                a[i].r /= n;
    }
    void mul(int* a, int* b, int* c,int k)
    {
        int i, j = __builtin_ctz(k) - 1;
        for(i=0; i<k; i++)
            pos[i] = pos[i>>1] >> 1 | ((i & 1) << j);
        for(i =0; i<k;i++)
            A[i] = comp(a[i], b[i]);
        FFT(A, k , 1);
        for(i=0; i<k; i++)
        {
            j = (k-i) & (k-1);
            B[i] = (A[i] * A[i] - (A[j] * A[j]).conj() ) * comp(0, -0.25);
        }
        FFT(B, k, -1);
        for(i=0; i<k; i++)
            c[i] = (long long) (B[i].r + 0.5) % FFT_MOD;
    }
}

int a[MAXN];
int b[MAXN];
int c[MAXN];
int d[MAXN];
int dp[MAXN];

void cdq(int l, int r)
{
    if(l == r)
    {
        dp[r] = (dp[r] + a[r]) % MOD;
        return;
    }
    int mid=l+r>>1, i, len=1;
    cdq(l, mid);
    while(len < r - l + 1)
        len <<= 1;
    for(i=0; i<len; i++)
    {
        b[i] = l + i > mid ? 0 : dp[l + i];
        c[i] = i < r ? a[i+1] : 0;
    }
    FFT::mul(b, c, d, len);
    for(i=mid+1; i<=r; i++)
        dp[i] = (dp[i] + d[i-l-1]) % MOD;
    cdq(mid+1, r);
}

int main()
{
    int n, i;
    while(scanf("%d", &n) && n)
    {
        for(i=1; i<=n; a[i++]%=MOD)
            scanf("%d", a+i);
        memset(dp, 0, sizeof dp);
        cdq(1, n);
        printf("%d\n", dp[n]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值