CF848E Days of Floral Colours
XVIII.CF848E Days of Floral Colours
大部分FFT题都是用来优化DP的……
首先,我们看向环上的某个位置(自动对取模):
它有如下几种配对:
-
。
-
。
-
。
而其中方法3,它中间的位置,就必须放置一个方法1或方法3。
于是我们最终可以分成4类:
-
,它占用临近的个格子。
-
,它占用临近的个格子。
-
,它占用临近的个格子。
-
,它占用临近的个格子。
于是显而易见地,我们将其分别命名为法1到法4。而它们又可以分成两类,类型1是(法1、法3),它们会将大圆分成两段;而类型2是(法2、法4),它们不会对大圆的分段产生任何影响。
显然,这张图上,必定存在一个类型1的位置(不然按照“美感”的定义,这张图的美感为)。我们于是将整张图重新标号,强制令这一对是按照类型1中某一个方案连接的。
在这么考虑后,我们就发现因为这个圆是“对称”的,所以我们只需要考虑一个半圆的分配方式即可。而一个半圆,就可以看作一段连续的位置。
我们可以设一个表示一段长度为的位置所有可能的划分方案的美感之和。为了不重不漏地考虑到每一种方案,我们应该选取最靠左的一个放置了类型1的位置进行转移。
则我们如果将这一段位置依次标号为的话,我们设最靠左的类型1在位置。
我们先来考虑往位置放上了法1的情况。则中不应该有任何类型1存在,这显然是一种新的状态(只能填充类型2的情况),我们设一个来表示这种情况的方案数。
是很好求出的——可以直接地DP出来。即,,,其中两种转移分别对应了放置法2和法4。
于是我们往位置放置法1的情况就可以直接表示成
其中的来历是因为有大圆关于对称,所以有两段。
下标从开始比较令人不爽,我们令其从开始:
等等,这不是卷积的形式吗?分治FFT优化走起。
但是先别急,类型1还有一种可能,即位置放上了法3。
这时,我们会发现左边一段的贡献是,因为法3所占用的个位置中,左右两个位置是算在左右两段中的,但是被固定死了,所以是。
但是右边一段呢?我们会发现,右边一段最左边的位置一样被固定死了。这显然同我们的定义不同。我们必须设一种新的状态来表示。
我们设之前的为,而这里一端被固定死的,我们称其为。我们这里的中的,是不包含左端被固定死的位置的。
则我们就有后半段的贡献是
同之前一样,我们令下标从开始
则汇总得
等等,我们是不是忘了什么?
没错,我们忘记了可能一个类型1的位置都没有。则此时的方案就是的方案数。
所以最终的转移方程就是
现在再看回。显然它可以类似地求得——不,准确来说,它的方程和完全一致——除了最左端分出的那一段的长度应该以外。
于是就有
显然,和都可以通过分治FFT一起在的时间内求出。
但是我们会发现,因为这是一个环,所以当你最终统计答案的时候,是会出现两端都是法3的特殊区间的。这启发我们还要再设一个来表示两端都被固定死的区间。
类似地,我们有
汇总起来就是
下面就是最终求答案的时候了。
一种情况是存在且只存在一个类型1。这时的答案就是
其中是因为有法1和法3两种情况,是因为这时分出的两段的长度都是,而是因为有个位置可以放置。
而第二种情况,就是存在多个类型。
为了不重不漏地枚举,我们枚举位置所在的那段的长度,为。
则可以从到。
则这一段的两边都是类型1,可能是法1或法3。
如果都是法1的话,就是;
如果有一个是法3的话,就是;但是因为左右两边都可能是法3,故应乘二。
如果两个都是法3,就是。
但是这三种外面都要再乘上一个,因为本身的长度是(对称的两段),又有种放置方法。
于是总答案就是
或者也可以稍微改变一下下标,使其更为对称:
时间复杂度。
代码:
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?