题解 CF1349F2 【Slime and Sequences (Hard Version)】
command_block · · 题解
官方题解比较简略,这里我来补充一个详细一点的。
此题 : 国际计数水平.jpg
题意 : 定义好序列为 : 都是正整数 , 如果某个数
现在对
-
Easy\ n\leq 5000 , 时限\texttt{2s} -
Hard\ n\leq 10^5 , 时限\texttt{3s}
中 国 特 色
计数工业总产值跃居世界第一!
这位老哥您先别走,能不能告诉我 GF 是什么意思, 拉格朗日反演又是啥?
0.前置芝士
-
分式域
-
(扩展)拉格朗日反演
-
欧拉数
-
二元
GF
1.转化
这个好序列的限制十分刻意,首先考虑转化一下。
事实上,可以建立起长度为
在排列
-
证明其满足好序列的条件 :
考虑一个
< ,以此为分界线,前面为p_{i-1} ,后面为p_{i} 。有a_{p_{i-1}}+1=a_{p_i} ,因为中间隔了一个小于号。同时又有
p_{i-1}<p_i (位置) ,也是因为小于号。实际上小于号就是把两个限制结合在了一起。
通过这个构造,也容易从好序列还原排列。具体证明略。
学长说这可以入选“十大炫酷映射”
以本人的憨憨水平,考场上根本想不出来这种精妙的构造。甚至还根本没去想等价转化,直接对着原问题分析,连正解的边都没摸着,直接被拒之门外。这可能也就是我正式比赛中没切过任何一道计数工业的原因吧……
目前大概想到了几点。
首先如果原问题复杂(条件信息量太大),感觉不可做,那么大概率是出题人做了神仙转化,要去寻找构造或者充要条件。
其次,可以统计需要计数的东西的总数,比如这个题,如果发现总数是
2.初步分析
现在就是要对排列统计
设
我们分别考虑每个位置的贡献。
意思是 : 枚举在好序列中产生
其实只有
存在一个Easy版。
#include<algorithm>
#include<cstdio>
#define MaxN 5050
#define ll long long
using namespace std;
const int mod=998244353;
ll powM(ll a,int t=mod-2){
ll ret=1;
while(t){
if (t&1)ret=ret*a%mod;
a=a*a%mod;t>>=1;
}return ret;
}
ll fac[MaxN],ifac[MaxN],s[MaxN],s2[MaxN],ans[MaxN];
int n;
int main()
{
scanf("%d",&n);
fac[0]=1;
for (int i=1;i<=n;i++)
fac[i]=fac[i-1]*i%mod;
ifac[n]=powM(fac[n]);
for (int i=n;i;i--)
ifac[i-1]=ifac[i]*i%mod;
s2[0]=1;
for (int i=1;i<=n;i++){
s[0]=1;ans[0]=(ans[0]+ifac[i])%mod;
for (int j=1;j<=i;j++){
s[j]=(s2[j]*(j+1)+s2[j-1]*(i-j))%mod;
ans[j]=(ans[j]+s[j]*ifac[i])%mod;
}for (int j=0;j<=i;j++)s2[j]=s[j];
}for (int i=0;i<n;i++)
printf("%lld ",ans[i]*fac[n]%mod);
return 0;
}
3.加速计算
这个式子涉及到了
先设
那么,根据二项式反演,非常经典地有
我们考虑
考虑单个块的EGF,为
如果恰好需要
这是个有序组合并分配标号,能得到
则
代入Easy的式子中,得到
设
得知
-
观察
S[k]=\sum\limits_{i=k}^n[x^i](e^x-1)^{i-k} =\sum\limits_{i=k}^n[x^k]\Big(\dfrac{e^x-1}{x}\Big)^{i-k} =\sum\limits_{i=0}^{n-k}[x^k]\Big(\dfrac{e^x-1}{x}\Big)^{i} 形式统一了许多。
设
我们的目标就是对
第一部分可以多项式求逆,但是注意到分母的常数项为
-
考虑修改求逆的定义 :
G(x)=x^kF(x) , 使得F(x) 有常数项,那么\dfrac{1}{G(x)} = x^{-k}\dfrac{1}{F(x)} 附 : 这需要引入分式域(负数次数),拉格朗日反演在这里仍然成立,所以不用担心干扰推导。
通俗地讲,就是先把整个式子乘以
x^k ,相当于对分母除x^k ,然后可以经典求逆。提取系数的时候要考虑多余的x^k ,提取高k 位,所以多项式的界要大一点。这里实际上最低只有
[x^{-1}] 项,所以也不是很麻烦。
接下来考虑第二部分。(分母的逆同样拥有负指数,不过没关系)
把次数补齐 :
考虑加入一个元以区分信息。
注意到右边是与
考虑使用拉格朗日反演,多项式复合系列理论。
但注意到
先设
(我们使用
又设
- 注 : 手动爆算可求得
G'(x)=\dfrac{y+H'(x)-yH(x)-xyH'(x)}{\big(1-H(x)\big)^2\big(1-xy\big)^2}
那么有
(现在我们以经典多项式复合描述了问题)
现在的问题就是
设
使用扩展拉格朗日反演可得
忽略常数
展开关于
考虑提取
分别求出
常规的多项式复合逆肯定是不行的,注意到问题的特殊性 :
容易构造
( 此处有
综上,我们以
实现中使用了大力倍增快速幂,时限比较松就卡过去了。
#include<algorithm>
#include<cstring>
#include<cstdio>
#define ll long long
#define clr(f,n) memset(f,0,sizeof(ll)*(n))
#define cpy(f,g,n) memcpy(f,g,sizeof(ll)*(n))
using namespace std;
const int _G=3,mod=998244353,Maxn=135000;
ll powM(ll a,ll t=mod-2){
ll ans=1;
while(t){
if(t&1)ans=ans*a%mod;
a=a*a%mod;t>>=1;
}return ans;
}
const int invG=powM(_G);
int tr[Maxn<<1],tf;
void tpre(int n){
if (tf==n)return ;tf=n;
for(int i=0;i<n;i++)
tr[i]=(tr[i>>1]>>1)|((i&1)?n>>1:0);
}
void NTT(ll *g,bool op,int n)
{
#define ull unsigned long long
tpre(n);
static ull f[Maxn<<1],w[Maxn]={1};
for (int i=0;i<n;i++)f[i]=(((ll)mod<<5)+g[tr[i]])%mod;
for(int l=1;l<n;l<<=1){
ull tG=powM(op?_G:invG,(mod-1)/(l+l));
for (int i=1;i<l;i++)w[i]=w[i-1]*tG%mod;
for(int k=0;k<n;k+=l+l)
for(int p=0;p<l;p++){
int tt=w[p]*f[k|l|p]%mod;
f[k|l|p]=f[k|p]+mod-tt;
f[k|p]+=tt;
}
if (l==(1<<10))
for (int i=0;i<n;i++)f[i]%=mod;
}if (!op){
ull invn=powM(n);
for(int i=0;i<n;++i)
g[i]=f[i]%mod*invn%mod;
}else for(int i=0;i<n;++i)g[i]=f[i]%mod;
}
void px(ll *f,ll *g,int n)
{for(int i=0;i<n;++i)f[i]=f[i]*g[i]%mod;}
void times(ll *f,ll *g,int m)
{
int n=1;while(n<m+m)n<<=1;
NTT(f,1,n);NTT(g,1,n);
px(f,g,n);NTT(f,0,n);clr(f+m,n-m);
}
void invp(ll *f,int m)
{
static ll sav[Maxn<<1],w[Maxn<<1],r[Maxn<<1];
int n;for (n=1;n<m;n<<=1);
w[0]=powM(f[0]);
for (int len=2;len<=n;len<<=1){
for (int i=0;i<(len>>1);i++)
r[i]=(w[i]<<1)%mod;
cpy(sav,f,len);
NTT(w,1,len<<1);px(w,w,len<<1);
NTT(sav,1,len<<1);px(w,sav,len<<1);
NTT(w,0,len<<1);clr(w+len,len);
for (int i=0;i<len;i++)
w[i]=(r[i]-w[i]+mod)%mod;
}cpy(f,w,m);clr(sav,n+n);clr(w,n+n);clr(r,n+n);
}
void dao(ll *f,int m){
for (int i=1;i<m;i++)
f[i-1]=f[i]*i%mod;
f[m-1]=0;
}
void powp(ll *f,int m,int t)
{
static ll g[Maxn<<1];
int n=1;while(n<m+m)n<<=1;g[0]=1;
while(t){
NTT(f,1,n);
if (t&1){
NTT(g,1,n);
px(g,f,n);NTT(g,0,n);
clr(g+m,n-m);
}px(f,f,n);NTT(f,0,n);
clr(f+m,n-m);t>>=1;
}cpy(f,g,m);clr(f+m,n-m);clr(g,n);
}
ll fac[Maxn],ifac[Maxn];
void Init(int m)
{
fac[0]=1;
for (int i=1;i<=m;i++)
fac[i]=fac[i-1]*i%mod;
ifac[m]=powM(fac[m]);
for (int i=m;i;i--)
ifac[i-1]=ifac[i]*i%mod;
}
int n,m;
ll F[Maxn<<1],G[Maxn<<1],S[Maxn<<1],H[Maxn<<1],H1[Maxn<<1],H2[Maxn<<1];
int main()
{
scanf("%d",&n);Init(n+6);
int len=1;while(len<n+n+4)len<<=1;
m=n+2;
for (int i=0;i<m+1;i++)G[i]=mod-ifac[i+2];
invp(G,m+1);cpy(S,G+1,m+1);
for (int i=0;i<m+1;i++){
H[i]=fac[i]*ifac[i+1]%mod;
if (i&1)H[i]=mod-H[i];
}invp(H,m+1);cpy(H1,H,m);
powp(H1,m,n+1);
cpy(G,H,m+1);dao(G,m+1);
cpy(H2,H1,m);times(H2,G,m);clr(G,len);
for (int i=0;i<m;i++)G[i]=mod-H[i+1];
invp(G,m);
NTT(G,1,len);NTT(H1,1,len);NTT(H2,1,len);
px(H1,G,len);NTT(H1,0,len);clr(H1+m,len-m);
px(H2,G,len);NTT(H2,0,len);clr(H2+m,len-m);NTT(H2,1,len);
px(H2,G,len);NTT(H2,0,len);clr(H2+m,len-m);
for (int i=1,sav=powM(n+1);i<n+2;i++){
int m=n-i+1;
S[i]=(S[i]-(H1[n-m+2]*m+H2[n-m+2])%mod*sav)%mod;
}S[0]=n;
for (int i=0;i<n;i++)S[i]=(S[i]+mod)%mod*fac[i]%mod;
for (int i=0;i<n;i++)F[i]=(i&1) ? mod-ifac[i] : ifac[i];
clr(F+n,len-n);clr(S+n,len-n);reverse(S,S+n);
NTT(F,1,len);NTT(S,1,len);
px(S,F,len);NTT(S,0,len);
clr(S+n,len-n);reverse(S,S+n);
for (int i=0;i<n;i++)
printf("%lld ",S[i]*fac[n]%mod*ifac[i]%mod);
return 0;
}