CF848E Days of Floral Colours
XVIII.CF848E Days of Floral Colours
大部分FFT题都是用来优化DP的……
首先,我们看向环上的某个位置\(i\)(自动对\(2n\)取模):
它有如下几种配对:
-
\((i,i+n)\)。
-
\((i,i\pm1)\)。
-
\((i,i\pm2)\)。
而其中方法3,它中间的位置\(i\pm1\),就必须放置一个方法1或方法3。
于是我们最终可以分成4类:
-
\((i,i+n)\),它占用临近的\(1\)个格子。
-
\((i,i\pm1)\),它占用临近的\(2\)个格子。
-
\((i,i\pm2)+(i\pm1,i\pm1+n)\),它占用临近的\(3\)个格子。
-
\((i,i\pm2)+(i\pm1,i\pm1\pm2)\),它占用临近的\(4\)个格子。
于是显而易见地,我们将其分别命名为法1到法4。而它们又可以分成两类,类型1是(法1、法3),它们会将大圆分成两段;而类型2是(法2、法4),它们不会对大圆的分段产生任何影响。
显然,这张图上,必定存在一个类型1的位置(不然按照“美感”的定义,这张图的美感为\(0\))。我们于是将整张图重新标号,强制令\((1,n+1)\)这一对是按照类型1中某一个方案连接的。
在这么考虑后,我们就发现因为这个圆是“对称”的,所以我们只需要考虑一个半圆的分配方式即可。而一个半圆,就可以看作一段连续的位置。
我们可以设一个\(f_i\)表示一段长度为\(i\)的位置所有可能的划分方案的美感之和。为了不重不漏地考虑到每一种方案,我们应该选取最靠左的一个放置了类型1的位置进行转移。
则我们如果将这一段位置依次标号为\(1,2,\dots,i\)的话,我们设最靠左的类型1在位置\(j\)。
我们先来考虑往位置\(j\)放上了法1的情况。则\((1,j-1)\)中不应该有任何类型1存在,这显然是一种新的状态(只能填充类型2的情况),我们设一个\(g_i\)来表示这种情况的方案数。
\(g_i\)是很好求出的——可以直接\(O(n)\)地DP出来。即,\(g_0=1\),\(g_i=g_{i-2}+g_{i-4}\),其中两种转移分别对应了放置法2和法4。
于是我们往位置\(j\)放置法1的情况就可以直接表示成
其中\((j-1)^2\)的来历是因为有大圆关于\((1,n+1)\)对称,所以有两段。
下标从\(1\)开始比较令人不爽,我们令其从\(0\)开始:
等等,这不是卷积的形式吗?分治FFT优化走起。
但是先别急,类型1还有一种可能,即位置\(j\)放上了法3。
这时,我们会发现左边一段的贡献是\((j-1)^2\times g_{j-2}\),因为法3所占用的\(3\)个位置中,左右两个位置是算在左右两段中的,但是被固定死了,所以是\(g_{j-2}\)。
但是右边一段呢?我们会发现,右边一段最左边的位置一样被固定死了。这显然同我们\(f_i\)的定义不同。我们必须设一种新的状态来表示。
我们设之前的\(f_i\)为\(f_0[i]\),而这里一端被固定死的,我们称其为\(f_1[i]\)。我们这里的\(f_1[i]\)中的\(i\),是不包含左端被固定死的位置的。
则我们就有后半段的贡献是
同之前一样,我们令下标从\(0\)开始
则汇总得
等等,我们是不是忘了什么?
没错,我们忘记了可能一个类型1的位置都没有。则此时的方案就是\(g_i\)的方案数。
所以最终的转移方程就是
现在再看回\(f_1\)。显然它可以类似地求得——不,准确来说,它的方程和\(f_0\)完全一致——除了最左端分出的那一段的长度应该\(+1\)以外。
于是就有
显然,\(f_0\)和\(f_1\)都可以通过分治FFT一起在\(O(n\log^2n)\)的时间内求出。
但是我们会发现,因为这是一个环,所以当你最终统计答案的时候,是会出现两端都是法3的特殊区间的。这启发我们还要再设一个\(f_2\)来表示两端都被固定死的区间。
类似地,我们有
汇总起来就是
下面就是最终求答案的时候了。
一种情况是存在且只存在一个类型1。这时的答案就是
其中\(g_{n-1}+g_{n-3}\)是因为有法1和法3两种情况,\((n-1)^2\)是因为这时分出的两段的长度都是\(n-1\),而\(n\)是因为有\(n\)个位置可以放置。
而第二种情况,就是存在多个类型\(1\)。
为了不重不漏地枚举,我们枚举位置\(1\)所在的那段的长度,为\(i\)。
则\(i\)可以从\(1\)到\(n-3\)。
则这一段的两边都是类型1,可能是法1或法3。
如果都是法1的话,就是\(g_i\times f_0[n-i-2]\);
如果有一个是法3的话,就是\(g_{i-1}\times f_1[n-i-3]\);但是因为左右两边都可能是法3,故应乘二。
如果两个都是法3,就是\(g_{i-2}\times f_2[n-i-4]\)。
但是这三种外面都要再乘上一个\(i^2\times(i+1)\),因为本身的长度是\(i^2\)(对称的两段),又有\(i+1\)种放置方法。
于是总答案就是
或者也可以稍微改变一下下标,使其更为对称:
时间复杂度\(O(n\log^2n)\)。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1<<20;
const int mod=998244353;
int n,g[N],f[3][N],all,res;
//g:the number of partitions when there is no separating point
//f0:the number of partitions that there could be separating points, and there is no matched places on either border
//f1:there is exact one matched place on one border
//f2:there're matched places on both borders
const int G=3;
int rev[N];
int ksm(int x,int y){
int rt=1;
for(;y;x=(1ll*x*x)%mod,y>>=1)if(y&1)rt=(1ll*rt*x)%mod;
return rt;
}
void NTT(int *a,int tp,int LG){
int lim=(1<<LG),invlim=ksm(lim,mod-2);
for(int i=0;i<lim;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(LG-1));
for(int i=0;i<lim;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
for(int md=1;md<lim;md<<=1){
int rt=ksm(G,(mod-1)/(md<<1));
if(tp==-1)rt=ksm(rt,mod-2);
for(int stp=md<<1,pos=0;pos<lim;pos+=stp){
int w=1;
for(int i=0;i<md;i++,w=(1ll*w*rt)%mod){
int x=a[pos+i],y=(1ll*w*a[pos+md+i])%mod;
a[pos+i]=(x+y)%mod;
a[pos+md+i]=(x-y+mod)%mod;
}
}
}
if(tp==-1)for(int i=0;i<lim;i++)a[i]=(1ll*a[i]*invlim)%mod;
}
#define sqr(x) 1ll*(x)*(x)%mod
void ADD(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int A[N],B[N];
void CDQ(int l,int LG){
if(!LG){ADD(f[0][l],sqr(l)*g[l]%mod),ADD(f[1][l],sqr(l+1)*g[l]%mod),ADD(f[2][l],sqr(l+2)*g[l]%mod);return;}
CDQ(l,LG-1);
int lim=(1<<LG);
//----------------------0------------------------------
for(int i=0;i<(lim<<1);i++)A[i]=B[i]=0;
for(int i=0;i<lim;i++)A[i]=sqr(i)*g[i]%mod;
for(int i=0;i<(lim>>1);i++)B[i]=f[0][l+i];
NTT(A,1,LG+1),NTT(B,1,LG+1);
for(int i=0;i<(lim<<1);i++)A[i]=1ll*A[i]*B[i]%mod;
NTT(A,-1,LG+1);
for(int i=(lim>>1);i<lim;i++)if(i-1>=0)ADD(f[0][l+i],A[i-1]);
for(int i=0;i<(lim<<1);i++)A[i]=B[i]=0;
for(int i=0;i<lim;i++)A[i]=sqr(i+1)*g[i]%mod;
for(int i=0;i<(lim>>1);i++)B[i]=f[1][l+i];
NTT(A,1,LG+1),NTT(B,1,LG+1);
for(int i=0;i<(lim<<1);i++)A[i]=1ll*A[i]*B[i]%mod;
NTT(A,-1,LG+1);
for(int i=(lim>>1);i<lim;i++)if(i-3>=0)ADD(f[0][l+i],A[i-3]);
//----------------------1------------------------------
for(int i=0;i<(lim<<1);i++)A[i]=B[i]=0;
for(int i=0;i<lim;i++)A[i]=sqr(i+1)*g[i]%mod;
for(int i=0;i<(lim>>1);i++)B[i]=f[0][l+i];
NTT(A,1,LG+1),NTT(B,1,LG+1);
for(int i=0;i<(lim<<1);i++)A[i]=1ll*A[i]*B[i]%mod;
NTT(A,-1,LG+1);
for(int i=(lim>>1);i<lim;i++)if(i-1>=0)ADD(f[1][l+i],A[i-1]);
for(int i=0;i<(lim<<1);i++)A[i]=B[i]=0;
for(int i=0;i<lim;i++)A[i]=sqr(i+2)*g[i]%mod;
for(int i=0;i<(lim>>1);i++)B[i]=f[1][l+i];
NTT(A,1,LG+1),NTT(B,1,LG+1);
for(int i=0;i<(lim<<1);i++)A[i]=1ll*A[i]*B[i]%mod;
NTT(A,-1,LG+1);
for(int i=(lim>>1);i<lim;i++)if(i-3>=0)ADD(f[1][l+i],A[i-3]);
//----------------------2------------------------------
for(int i=0;i<(lim<<1);i++)A[i]=B[i]=0;
for(int i=0;i<lim;i++)A[i]=sqr(i+1)*g[i]%mod;
for(int i=0;i<(lim>>1);i++)B[i]=f[1][l+i];
NTT(A,1,LG+1),NTT(B,1,LG+1);
for(int i=0;i<(lim<<1);i++)A[i]=1ll*A[i]*B[i]%mod;
NTT(A,-1,LG+1);
for(int i=(lim>>1);i<lim;i++)if(i-1>=0)ADD(f[2][l+i],A[i-1]);
for(int i=0;i<(lim<<1);i++)A[i]=B[i]=0;
for(int i=0;i<lim;i++)A[i]=sqr(i+2)*g[i]%mod;
for(int i=0;i<(lim>>1);i++)B[i]=f[2][l+i];
NTT(A,1,LG+1),NTT(B,1,LG+1);
for(int i=0;i<(lim<<1);i++)A[i]=1ll*A[i]*B[i]%mod;
NTT(A,-1,LG+1);
for(int i=(lim>>1);i<lim;i++)if(i-3>=0)ADD(f[2][l+i],A[i-3]);
CDQ(l+(lim>>1),LG-1);
}
int main(){
scanf("%d",&n);
while((1<<all)<=n)all++;
g[0]=g[2]=1,g[1]=g[3]=0;
for(int i=4;i<(1<<all);i++)g[i]=(g[i-2]+g[i-4])%mod;
CDQ(0,all);
res=sqr(n-1)*n%mod*(g[n-1]+g[n-3])%mod;
for(int i=2;i<=n-2;i++){
if(i-1>=0&&n-i-1>=0)ADD(res,sqr(i-1)*i%mod*g[i-1]%mod*f[0][n-i-1]%mod);
if(i-2>=0&&n-i-2>=0)ADD(res,2*sqr(i-1)*i%mod*g[i-2]%mod*f[1][n-i-2]%mod);
if(i-3>=0&&n-i-3>=0)ADD(res,sqr(i-1)*i%mod*g[i-3]%mod*f[2][n-i-3]%mod);
}
printf("%d\n",res);
return 0;
}