HDU - 5730 CDQ分治 + FFT

本文介绍了一种利用快速傅立叶变换(FFT)和CDQ分治技巧优化动态规划(DP)的问题解决方法。针对特定类型的DP问题,通过巧妙地结合FFT加速多项式乘法与CDQ分治策略,将复杂度降低到O(nlog^2n),适用于求解序列划分的价值总和问题。

题意:

有一串长度为n的序列,可以任意划分,题目给出长度从1到n的块的价值,每一种划分的价值等于划分出来的所有块的价值的乘积,问所有划分方法得到的价值的总和是多少?

思路:

这题很容易就能得到状态转移方程,设dp[i]是长度为i的序列的价值总和,那么就有状态转移方程:
然而计算这样的状态转移需要花费O(n^2)的时间,不能满足要求。注意观察到这是一个离散卷积形式,可以用FFT来加速运算。
FFT可以加速多项式相乘系数的计算,这里也同样可以看作两个多项式A和B,将dp[j]看作是多项式A中j次项的系数,a[i-j]看作是多项式B中i-j次项的系数,dp[i]就可以看作是A和B相乘时得到的多项式C中i次项的系数,这样就符合了FFT的运算条件。但是如果对于每个dp[i]都用FFT运算,复杂度将达到O(n^2logn),显然更慢,所以这里需要用到CDQ分治。
CDQ(l,r)就是用来求出l到r的dp值,每次二分区间,先递归计算出(l,m)的dp值,再利用(l,m)的dp值来更新(m+1,r)的dp值,更新的过程使用FFT加速运算,算法总复杂度为O(nlognlogn)。
这里在计算(l,m)的贡献的时候,因为要使用FFT,需要将下标统一到1到len存在x1和x2中,注意细节。

代码:

#include <bits/stdc++.h>
using namespace std;
const double pi = acos(-1);
const int MAXN = 555555;
const int MOD = 313;

struct plex {       // 定义复数类
    double x, y;
    plex (double _x = 0.0, double _y = 0.0) : x (_x), y (_y) {}
    plex operator + (const plex &a) const {
        return plex (x + a.x, y + a.y);
    }
    plex operator - (const plex &a) const {
        return plex (x - a.x, y - a.y);
    }
    plex operator * (const plex &a) const {
        return plex (x * a.x - y * a.y, x * a.y + y * a.x);
    }
};

void change (plex *y, int len) {
    for (int i = 1, j = len / 2; i < len - 1; i++) {
        if (i < j) swap(y[i], y[j]);
        int k = len / 2;
        while (j >= k) {
            j -= k;
            k /= 2;
        }
        if (j < k) j += k;
    }
}

void fft(plex y[], int len, int on) {   // FFT过程,on==1时,将系数表达转换成点值表达,on==-1时,将点值表达转换成系数表达
    change(y, len);
    for(int h = 2; h <= len; h <<= 1) {
        plex wn(cos(-on * 2 * pi / h), sin(-on * 2 * pi / h));
        for(int j = 0; j < len; j += h) {
            plex w(1, 0);
            for(int k = j; k < j + h / 2; k++) {
                plex u = y[k];
                plex t = w * y[k + h / 2];
                y[k] = u + t;
                y[k + h / 2] = u - t;
                w = w * wn;
            }
        }
    }
    if(on == -1) {
        for(int i = 0; i < len; i++)
            y[i].x /= len;
    }
}

int a[MAXN], dp[MAXN];
plex x1[MAXN], x2[MAXN];

void cdq(int l, int r) {
    if (l == r) return;
    int m = (r - l) / 2 + l;
    cdq(l, m);
    int len = 2;
    while (len < (r - l + 1) * 2) len <<= 1;
    for (int i = 0; i < len; i++)
        x1[i] = x2[i] = plex(0, 0);
    for (int i = l; i <= m; i++)
        x1[i - l + 1] = plex(dp[i], 0);
    for (int i = 1; i <= r - l; i++)
        x2[i] = plex(a[i], 0);
    fft(x1, len, 1);
    fft(x2, len, 1);
    for (int i = 0; i < len; i++)
        x1[i] = x1[i] * x2[i];
    fft(x1, len, -1);
    for (int i = m + 1; i <= r; i++)
        dp[i] = (dp[i] + (int)(x1[i - l + 1].x + 0.5)) % MOD;
    cdq(m + 1, r);
}

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



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值