NKOJ 3933 贝壳串(CDQ分治+FFT)

本文介绍了一种使用CDQ分治与FFT快速傅里叶变换相结合的方法,解决贝壳串组合计数问题,即求解不同长度贝壳串组合成特定长度的所有可能方案数。

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

P3933贝壳串

问题描述

海边市场有长度分别为1到n的贝壳串出售,其中长度为i的贝壳串有a[i]种,每种贝壳串有无限个,问用这些贝壳串链接成长度为n的串有多少种方案?

输入格式

第一行,一整数n,
第二行,n个整数ai表示长度为i的贝壳串的种类数
(n<=10^5,0<=ai<=10^7)

输出格式

输出方案数,结果模313

样例输入 1

3
1 3 7

样例输出 1

14

样例输入 2

4
2 2 2 2

样例输出 2

54


dp方程比较显然,令F[i]F[i]表示组成长度为i的串的方案数,那么F[i]=ik=0A[k]×F[ik]F[i]=∑k=0iA[k]×F[i−k]
直接递推是n2n2的,但是我们注意到这个转移式子是一个卷积,那么可以用FFTFFT来加速,但直接FFTFFT显然非常不优秀,需要用到CDQCDQ来解决。

假设要算F[l]F[r]F[l]−F[r]的dp值,那么可以先处理出F[l]F[mid]F[l]−F[mid]的dp值,然后做一个卷积算出F[l]F[mid]F[l]−F[mid]F[mid+1]F[r]F[mid+1]−F[r]的贡献,这里用FFTFFT处理,然后递归计算F[mid+1]F[r]F[mid+1]−F[r]的dp值。

由于每次做FFTFFT的长度是和区间长度相关的,因此总时间复杂度O(nlog2n)O(nlog2⁡n)


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<complex>
#define N 500000
using namespace std;
const int mod=313;
const double pi=4.0*atan(1.0);
int F[N],a[N];
complex<double>A[N],B[N],C[N],D[N],wi[N];
void FFT(complex<double>W[],int n,int ty)
{
    int i,j,k,m;
    complex<double>t0,t1;
    for(i=j=0;i<n;i++)
    {
        if(i<j)swap(W[i],W[j]);
        for(k=n>>1;(j^=k)<k;k>>=1);
    }
    wi[0]=1;
    for(m=1;m<n;m<<=1)
    {
        t0=exp(complex<double>(0,ty*pi/m));
        for(i=1;i<m;i++)wi[i]=wi[i-1]*t0;
        for(k=0;k<n;k+=m<<1)
        for(i=k;i<k+m;i++)
        {
            t0=W[i];
            t1=W[i+m]*wi[i-k];
            W[i]=t0+t1;
            W[i+m]=t0-t1;
        }
    }
    if(ty==1)return;t0=1.0/n;
    for(i=0;i<n;i++)W[i]*=t0;
}
void CDQ(int l,int r)
{
    int n=1,i,j,k,mid=l+r>>1,l1,l2;
    if(l==r)return;
    CDQ(l,mid);
    l1=mid-l+1;l2=r-mid;
    while(n<=l1+l2)n<<=1;
    fill(A,A+n+n,0);
    fill(B,B+n+n,0);
    for(i=l;i<=mid;i++)A[i-l]=F[i];
    for(i=0;i<=r-l;i++)B[i]=a[i];
    FFT(A,n,1);FFT(B,n,1);
    for(i=0;i<n;i++)A[i]=A[i]*B[i];
    FFT(A,n,-1);
    for(i=mid+1;i<=r;i++)F[i]+=floor(A[i-l].real()+0.5),F[i]%=mod;
    CDQ(mid+1,r);
}
int main()
{
    int i,x,n;
    scanf("%d",&n);F[0]=1;
    for(i=1;i<=n;i++)scanf("%d",&a[i]),a[i]%=mod;
    CDQ(0,n);
    printf("%d",(F[n]+mod)%mod);
}
### CDQ分治法的基本概念 CDQ分治是一种基于分治思想的算法策略,最初由陈丹琦引入国内算法竞赛领域,因此被称为CDQ分治。该方法的核心思想是将问题划分为若干子问题,并递归地解决这些子问题。在解决子问题的同时,处理左半部分对右半部分的影响,从而逐步构建最终解[^1]。 CDQ分治的关键特征在于其递归结构:首先递归处理左半区间和右半区间的子问题,随后处理左区间对右区间的影响。这种策略通常利用排序来制造单调性,从而降低计算复杂度[^2]。 ### CDQ分治法的工作原理 CDQ分治的工作流程可以分为以下三个步骤: 1. **划分**:将原始问题划分为两个子问题,通常是对数组进行二分,分别处理左半区间和右半区间。 2. **处理**:计算左半区间对右半区间的影响。这一过程通常涉及对数据进行排序,以利用单调性减少重复计算[^3]。 3. **合并**:递归地处理右半区间的问题,并将结果整合。 以归并排序求逆序对为例,在合并两个子区间的过程中,需要计算左边区间对右边区间的影响。具体来说,当从右子区间中取出一个元素时,统计左边区间中比该元素大的元素数量,从而得到逆序对的个数。这一过程体现了CDQ分治的核心思想[^3]。 ### CDQ分治法的应用场景 CDQ分治广泛应用于解决多维偏序问题,例如二维偏序、三维偏序等。对于二维偏序问题,可以通过CDQ分治结合排序和树状数组来高效求解。在处理三维偏序问题时,CDQ分治通常与树状数组结合,以达到 $ O(n \log^2 n) $ 的时间复杂度[^5]。 此外,CDQ分治也常用于动态规划优化问题。例如,在求解最长递增子序列问题时,可以利用CDQ分治处理条件约束,例如 $ f_i = \max\{f_j + 1 \mid j < i, r_j \le a_i, a_j \le l_i\} $,其中 $ f_i $ 表示以第 $ i $ 个元素为结尾的最长子序列长度[^4]。 ### CDQ分治法的实现示例 以下是一个基于CDQ分治的伪代码示例,用于处理二维偏序问题: ```python def cdq_divide(l, r): if l == r: return mid = (l + r) // 2 cdq_divide(l, mid) cdq_divide(mid + 1, r) # 处理左区间对右区间的影响 # 例如:统计左区间中满足条件的元素对右区间的影响 # ... # 合并两个子区间并排序 # ... ``` 在具体实现中,通常需要结合归并排序的思想,以确保数据在分治过程中保持有序,从而提高效率[^5]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值