测试地址:Shell Necklace
题目大意:一串链形贝壳项链(不是环形),有种方案装饰连续个贝壳,问装饰整串项链有多少种方案。
做法:本题需要用到CDQ分治+FFT(分治FFT)。
首先令为装饰长为的项链的方案数,特殊地,令,那么我们很快能得出状态转移方程:
直接计算这个方程是的,不能接受。注意到等号右边的部分是一个卷积形式的式子,但是这个卷积和往常普通FFT可以做的卷积不同,它涉及到自己和另一个数组的卷积,所以我们不能用普通的FFT来解决问题。
这时候分治FFT就出场了。分治FFT也可以写成CDQ分治+FFT。我们知道CDQ分治的主要思想就是对于每个区间,先递归计算区间中的答案,然后计算对中答案的贡献,最后递归计算区间。这里我们采用类似的过程,先算出区间中所有的,然后将这一段和做卷积,求出它们对区间中所有的贡献,用FFT即可,最后计算区间。根据主定理,这个算法的时间复杂度是的,可以通过此题。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=313;
const double pi=acos(-1.0);
int n,rev[400010];
ll A[100010],f[100010];
struct Complex
{
double x,y;
}a[400010],b[400010];
Complex operator + (Complex a,Complex b) {Complex s={a.x+b.x,a.y+b.y};return s;}
Complex operator - (Complex a,Complex b) {Complex s={a.x-b.x,a.y-b.y};return s;}
Complex operator * (Complex a,Complex b) {Complex s={a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};return s;}
void FFT(Complex *a,int type,int n)
{
for(int i=0;i<n;i++)
if (i<rev[i]) swap(a[i],a[rev[i]]);
for(int mid=1;mid<n;mid<<=1)
{
Complex W={cos(pi/mid),type*sin(pi/mid)};
for(int l=0;l<n;l+=(mid<<1))
{
Complex w={1.0,0.0};
for(int k=0;k<mid;k++,w=w*W)
{
Complex x=a[l+k],y=w*a[l+mid+k];
a[l+k]=x+y;
a[l+mid+k]=x-y;
}
}
}
if (type==-1)
{
for(int i=0;i<n;i++)
a[i].x/=(double)n;
}
}
void solve(int l,int r)
{
if (l==r) return;
int mid=(l+r)>>1;
solve(l,mid);
int bit=0,x=1;
while(x<((r-l+1)<<1)) x<<=1,bit++;
for(int i=0;i<x;i++)
a[i].x=a[i].y=b[i].x=b[i].y=0.0;
for(int i=0;i<mid-l+1;i++)
a[i].x=f[l+i];
for(int i=0;i<r-l;i++)
b[i].x=A[i+1];
rev[0]=0;
for(int i=1;i<x;i++)
rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
FFT(a,1,x),FFT(b,1,x);
for(int i=0;i<x;i++)
a[i]=a[i]*b[i];
FFT(a,-1,x);
for(int i=mid+1;i<=r;i++)
{
f[i]+=a[i-l-1].x+0.5;
f[i]%=mod;
}
solve(mid+1,r);
}
int main()
{
while(scanf("%d",&n)&&n)
{
memset(f,0,sizeof(f));
f[0]=1;A[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%lld",&A[i]);
A[i]%=mod;
}
solve(0,n);
printf("%lld
",f[n]);
}
return 0;
}