CF848E Days of Floral Colours

XVIII.CF848E Days of Floral Colours

大部分FFT题都是用来优化DP的……

首先,我们看向环上的某个位置i(自动对2n取模):

,(i2),(i1),i,(i+1),(i+2),

它有如下几种配对:

  1. (i,i+n)

  2. (i,i±1)

  3. (i,i±2)

而其中方法3,它中间的位置i±1,就必须放置一个方法1或方法3。


于是我们最终可以分成4类:

  1. (i,i+n),它占用临近的1个格子。

  2. (i,i±1),它占用临近的2个格子。

  3. (i,i±2)+(i±1,i±1+n),它占用临近的3个格子。

  4. (i,i±2)+(i±1,i±1±2),它占用临近的4个格子。

于是显而易见地,我们将其分别命名为法1到法4。而它们又可以分成两类,类型1是(法1、法3),它们会将大圆分成两段;而类型2是(法2、法4),它们不会对大圆的分段产生任何影响。


显然,这张图上,必定存在一个类型1的位置(不然按照“美感”的定义,这张图的美感为0)。我们于是将整张图重新标号,强制令(1,n+1)这一对是按照类型1中某一个方案连接的。

在这么考虑后,我们就发现因为这个圆是“对称”的,所以我们只需要考虑一个半圆的分配方式即可。而一个半圆,就可以看作一段连续的位置。

我们可以设一个fi表示一段长度为i的位置所有可能的划分方案的美感之和。为了不重不漏地考虑到每一种方案,我们应该选取最靠左的一个放置了类型1的位置进行转移。

则我们如果将这一段位置依次标号为1,2,,i的话,我们设最靠左的类型1在位置j


我们先来考虑往位置j放上了法1的情况。则(1,j1)中不应该有任何类型1存在,这显然是一种新的状态(只能填充类型2的情况),我们设一个gi来表示这种情况的方案数

gi是很好求出的——可以直接O(n)地DP出来。即,g0=1gi=gi2+gi4,其中两种转移分别对应了放置法2和法4。

于是我们往位置j放置法1的情况就可以直接表示成

j=1i(j1)2×gj1×fij

其中(j1)2的来历是因为有大圆关于(1,n+1)对称,所以有两段。

下标从1开始比较令人不爽,我们令其从0开始:

j=0i1j2×gj×fij1

等等,这不是卷积的形式吗?分治FFT优化走起。


但是先别急,类型1还有一种可能,即位置j放上了法3。

这时,我们会发现左边一段的贡献是(j1)2×gj2,因为法3所占用的3个位置中,左右两个位置是算在左右两段中的,但是被固定死了,所以是gj2

但是右边一段呢?我们会发现,右边一段最左边的位置一样被固定死了。这显然同我们fi的定义不同。我们必须设一种新的状态来表示。

我们设之前的fif0[i],而这里一端被固定死的,我们称其为f1[i]。我们这里的f1[i]中的i,是不包含左端被固定死的位置的。

则我们就有后半段的贡献是

j=2i1(j1)2×gj2×f1[ij1]

同之前一样,我们令下标从0开始

j=0i3(j+1)2×gj×f1[ij3]

则汇总得

f0[i]=j=0i1j2×gj×f0[ij1]+j=0i3(j+1)2×gj×f1[ij3]


等等,我们是不是忘了什么?

没错,我们忘记了可能一个类型1的位置都没有。则此时的方案就是gi的方案数。

所以最终的转移方程就是

f0[i]=i2gi+j=0i1j2gj×f0[ij1]+j=0i3(j+1)2gj×f1[ij3]


现在再看回f1。显然它可以类似地求得——不,准确来说,它的方程和f0完全一致——除了最左端分出的那一段的长度应该+1以外。

于是就有

f1[i]=(i+1)2gi+j=0i1(j+1)2gj×f0[ij1]+j=0i3(j+2)2gj×f1[ij3]

显然,f0f1都可以通过分治FFT一起在O(nlog2n)的时间内求出。


但是我们会发现,因为这是一个,所以当你最终统计答案的时候,是会出现两端都是法3的特殊区间的。这启发我们还要再设一个f2来表示两端都被固定死的区间。

类似地,我们有

f2[i]=(i+2)2gi+j=0i1(j+1)2gj×f1[ij1]+j=0i3(j+2)2gj×f2[ij3]

汇总起来就是

f0[i]=i2gi+j=0i1j2gj×f0[ij1]+j=0i3(j+1)2gj×f1[ij3]f1[i]=(i+1)2gi+j=0i1(j+1)2gj×f0[ij1]+j=0i3(j+2)2gj×f1[ij3]f2[i]=(i+2)2gi+j=0i1(j+1)2gj×f1[ij1]+j=0i3(j+2)2gj×f2[ij3]


下面就是最终求答案的时候了。

一种情况是存在且只存在一个类型1。这时的答案就是

(gn1+gn3)×(n1)2×n

其中gn1+gn3是因为有法1和法3两种情况,(n1)2是因为这时分出的两段的长度都是n1,而n是因为有n个位置可以放置。

而第二种情况,就是存在多个类型1

为了不重不漏地枚举,我们枚举位置1所在的那段的长度,为i

i可以从1n3

则这一段的两边都是类型1,可能是法1或法3。

如果都是法1的话,就是gi×f0[ni2]

如果有一个是法3的话,就是gi1×f1[ni3];但是因为左右两边都可能是法3,故应乘二。

如果两个都是法3,就是gi2×f2[ni4]

但是这三种外面都要再乘上一个i2×(i+1),因为本身的长度是i2(对称的两段),又有i+1种放置方法。

于是总答案就是

(gn1+gn3)×(n1)2×n+i=1n3i2(i+1)×(gi×f0[ni2]+gi1×f1[ni3]+gi2×f2[ni4])

或者也可以稍微改变一下下标,使其更为对称:

n(n1)2(gn1+gn3)+i=2n2i(i1)2(gi1f0[ni1]+gi2f1[ni2]+gi3f2[ni3])

时间复杂度O(nlog2n)

代码:

#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;
} 

posted @   Troverld  阅读(50)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示