• CF848E Days of Floral Colours


    XVIII.CF848E Days of Floral Colours

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

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

    \[\dots,(i-2),(i-1),i,(i+1),(i+2),\dots \]

    它有如下几种配对:

    1. \((i,i+n)\)

    2. \((i,i\pm1)\)

    3. \((i,i\pm2)\)

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


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

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

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

    3. \((i,i\pm2)+(i\pm1,i\pm1+n)\),它占用临近的\(3\)个格子。

    4. \((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的情况就可以直接表示成

    \[\sum\limits_{j=1}^i(j-1)^2\times g_{j-1}\times f_{i-j} \]

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

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

    \[\sum\limits_{j=0}^{i-1}j^2\times g_j\times f_{i-j-1} \]

    等等,这不是卷积的形式吗?分治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\),是不包含左端被固定死的位置的。

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

    \[\sum\limits_{j=2}^{i-1}(j-1)^2\times g_{j-2}\times f_1[i-j-1] \]

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

    \[\sum\limits_{j=0}^{i-3}(j+1)^2\times g_j\times f_1[i-j-3] \]

    则汇总得

    \[f_0[i]=\sum\limits_{j=0}^{i-1}j^2\times g_j\times f_0[i-j-1]+\sum\limits_{j=0}^{i-3}(j+1)^2\times g_j\times f_1[i-j-3] \]


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

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

    所以最终的转移方程就是

    \[f_0[i]=i^2g_i+\sum\limits_{j=0}^{i-1}j^2g_j\times f_0[i-j-1]+\sum\limits_{j=0}^{i-3}(j+1)^2g_j\times f_1[i-j-3] \]


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

    于是就有

    \[f_1[i]=(i+1)^2g_i+\sum\limits_{j=0}^{i-1}(j+1)^2g_j\times f_0[i-j-1]+\sum\limits_{j=0}^{i-3}(j+2)^2g_j\times f_1[i-j-3] \]

    显然,\(f_0\)\(f_1\)都可以通过分治FFT一起在\(O(n\log^2n)\)的时间内求出。


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

    类似地,我们有

    \[f_2[i]=(i+2)^2g_i+\sum\limits_{j=0}^{i-1}(j+1)^2g_j\times f_1[i-j-1]+\sum\limits_{j=0}^{i-3}(j+2)^2g_j\times f_2[i-j-3] \]

    汇总起来就是

    \[\boxed{\small\begin{matrix}f_0[i]=i^2g_i+\sum\limits_{j=0}^{i-1}j^2g_j\times f_0[i-j-1]+\sum\limits_{j=0}^{i-3}(j+1)^2g_j\times f_1[i-j-3]\\f_1[i]=(i+1)^2g_i+\sum\limits_{j=0}^{i-1}(j+1)^2g_j\times f_0[i-j-1]+\sum\limits_{j=0}^{i-3}(j+2)^2g_j\times f_1[i-j-3]\\f_2[i]=(i+2)^2g_i+\sum\limits_{j=0}^{i-1}(j+1)^2g_j\times f_1[i-j-1]+\sum\limits_{j=0}^{i-3}(j+2)^2g_j\times f_2[i-j-3]\end{matrix}} \]


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

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

    \[(g_{n-1}+g_{n-3})\times(n-1)^2\times n \]

    其中\(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\)种放置方法。

    于是总答案就是

    \[(g_{n-1}+g_{n-3})\times(n-1)^2\times n+\sum\limits_{i=1}^{n-3}i^2(i+1)\times(g_i\times f_0[n-i-2]+g_{i-1}\times f_1[n-i-3]+g_{i-2}\times f_2[n-i-4]) \]

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

    \[n(n-1)^2(g_{n-1}+g_{n-3})+\sum\limits_{i=2}^{n-2}i(i-1)^2(g_{i-1}f_0[n-i-1]+g_{i-2}f_1[n-i-2]+g_{i-3}f_2[n-i-3]) \]

    时间复杂度\(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;
    } 
    

  • 相关阅读:
    我们为何要使用多线程,它有什么优点?
    Java并发和多线程那些事儿
    【BJG吐槽汇】第2期
    【BJG吐槽汇】第一期
    360:且用且珍惜!解决虚拟机linux启动缓慢以及ssh端卡顿的问题!
    多个不同的app应用间应该如何进行消息推送呢?
    JSONResult 封装
    MySQL 优化集锦
    学习bootstrap3
    开发一个响应式的静态网站---实战
  • 原文地址:https://www.cnblogs.com/Troverld/p/14608018.html
Copyright © 2020-2023  润新知