题解 CF1349F2 【Slime and Sequences (Hard Version)】

command_block

2020-05-15 19:45:17

Solution

官方题解比较简略,这里我来补充一个详细一点的。 此题 : 国际计数水平.jpg **题意** : 定义好序列为 : 都是正整数 , 如果某个数 $k$ 出现过 , 那么一定有 $k-1$ 出现在其前面。 现在对 $k=1...n$ ,分别统计其在长度为 $n$ 的所有好序列中,出现次数的和。 - `Easy` $\ n\leq 5000$ , 时限$\texttt{2s}$ - `Hard` $\ n\leq 10^5$ , 时限$\texttt{3s}$ ------------ 中 国 特 色 计数工业总产值跃居世界第一! 这位老哥您先别走,能不能告诉我 GF 是什么意思, 拉格朗日反演又是啥? ## 0.前置芝士 - 分式域 - (扩展)拉格朗日反演 - 欧拉数 - 二元 `GF` ## 1.转化 这个好序列的限制十分刻意,首先考虑转化一下。 事实上,可以建立起长度为 $n$ 的排列和长度为 $n$ 的好序列之间的**对应关系**。 在排列 $p$ 相邻数之间填写不等号,序列中 $a_{p_i}$ 的值就是 $p_{1...i}$ 之间的小于号个数$+1$。 - 证明其满足好序列的条件 : 考虑一个 $<$ ,以此为分界线,前面为 $p_{i-1}$ ,后面为 $p_{i}$ 。有 $a_{p_{i-1}}+1=a_{p_i}$ ,因为中间隔了一个小于号。 同时又有 $p_{i-1}<p_i$(位置) ,也是因为小于号。实际上小于号就是把两个限制结合在了一起。 通过这个构造,也容易从好序列还原排列。具体证明略。 ~~学长说这可以入选“十大炫酷映射”~~ 以本人的憨憨水平,考场上根本想不出来这种精妙的构造。甚至还根本没去想等价转化,直接对着原问题分析,连正解的边都没摸着,直接被拒之门外。这可能也就是我正式比赛中没切过任何一道计数工业的原因吧…… 目前大概想到了几点。 首先如果原问题复杂(条件信息量太大),感觉不可做,那么大概率是出题人做了神仙转化,要去寻找构造或者充要条件。 其次,可以统计需要计数的东西的总数,比如这个题,如果发现总数是 $n!$ ,就更容易想到构造成排列。 ## 2.初步分析 现在就是要对排列统计 $<$ 的个数。不难发现其实就是 [P5825 排列计数](https://www.luogu.com.cn/problem/P5825) 中的**欧拉数**。 设 $\left\langle\begin{matrix}n \\ k\end{matrix}\right\rangle$ 为 : 长度为 $n$ 的排列,小于号的个数为 $k$ 的方案数。 我们分别考虑每个位置的贡献。 $${\rm Ans}[k]=\sum\limits_{i=\max(k,1)}^n\left\langle\begin{matrix}i \\ k\end{matrix}\right\rangle\dbinom{n}{i}(n-i)!$$ 意思是 : 枚举在好序列中产生 $k$ 的位置。此时整个排列的方案数,就是对局部排列进行标号分配。后面的部分没有顺序限制,还能随意排列。 其实只有 ${\rm Ans}[0]$ 会被 $\max(k,1)$ 影响,我们可以特判,下界就会变得更漂亮。 存在一个$O(n^2)$递推欧拉数的做法,然后暴力计算上式,即可通过`Easy`版。 ```cpp #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.加速计算 这个式子涉及到了$O(n^2)$个的大量欧拉数,显然分离考虑是不行的。我们需要把 “黑盒” 欧拉数换成更易于批量分析的形式。 先设 $\left|\begin{matrix}n \\ k\end{matrix}\right|$ 为 : 长度为 $n$ 的排列,钦定 $k$ 个小于号,其余随意的方案数(可能重复计数)。 那么,根据**二项式反演**,非常经典地有 $$\left|\begin{matrix}n \\ k\end{matrix}\right|=\sum\limits_{i=k}^n\dbinom{i}{k}\left\langle\begin{matrix}n \\ i\end{matrix}\right\rangle\quad\Longrightarrow\quad \left\langle\begin{matrix}n \\ k\end{matrix}\right\rangle=\sum\limits_{i=k}^n\dbinom{i}{k}(-1)^{i-k}\left|\begin{matrix}n \\ i\end{matrix}\right|$$ 我们考虑 $\left|\begin{matrix}n \\ k\end{matrix}\right|$ 如何计算,注意到一串**连续**的 $<$ 可以视作一块,然后块中放置数的顺序就是固定的。 考虑单个块的`EGF`,为 $\{0,1,1,1,\dots\}\longrightarrow e^x-1$ (不考虑空集) 如果恰好需要 $k$ 个 $<$ ,则有 $n-k$ 个块。 这是个**有序组合并分配标号**,能得到 $\left|\begin{matrix}n \\ k\end{matrix}\right|=n![x^n](e^x-1)^{n-k}$ 则 $\left\langle\begin{matrix}n \\ k\end{matrix}\right\rangle=n!\sum\limits_{i=k}^n\dbinom{i}{k}(-1)^{i-k}[x^n](e^x-1)^{n-i}$ 代入`Easy`的式子中,得到 $${\rm Ans}[k]=\sum\limits_{i=k}^n\dbinom{n}{i}(n-i)!i!\sum\limits_{j=k}^i\dbinom{j}{k}(-1)^{j-k}[x^i](e^x-1)^{i-j}$$ $$=n!\sum\limits_{i=k}^n\sum\limits_{j=k}^i\dbinom{j}{k}(-1)^{j-k}[x^i](e^x-1)^{i-j}$$ $$=\dfrac{n!}{k!}\sum\limits_{j=k}^n\dfrac{(-1)^{j-k}j!}{(j-k)!}\sum\limits_{i=j}^n[x^i](e^x-1)^{i-j}$$ 设$S[k]=\sum\limits_{i=k}^n[x^i](e^x-1)^{i-k}$ $$=\dfrac{n!}{k!}\sum\limits_{j=k}^n\dfrac{(-1)^{j-k}}{(j-k)!}S[j]j!$$ 得知 $S[0...k]$ 之后显然可以**差卷积**计算。 - 观察 $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}$ 形式统一了许多。 设 $F(x)=\dfrac{e^x-1}{x}$ 。 我们的目标就是对 $k=0...(n-1)$ 求出 $S[k]=\sum\limits_{i=0}^{n-k}[x^k]F(x)^{i}$ $=[x^k]\bigg(\dfrac{1-F(x)^{n-k+1}}{1-F(x)}\bigg)$ $=[x^k]\dfrac{1}{1-F(x)}-[x^k]\dfrac{F(x)^{n-k+1}}{1-F(x)}$ 第一部分可以多项式求逆,但是注意到分母的常数项为 $0$ ,不能直接上。 - 考虑修改求逆的定义 : $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}]$ 项,所以也不是很麻烦。 接下来考虑第二部分。(分母的逆同样拥有负指数,不过没关系) 把次数补齐 : $[x^k]\dfrac{F(x)^{n-k+1}}{1-F(x)}=[x^{n+1}]\dfrac{\big(xF(x)\big)^{n-k+1}}{1-F(x)}$ 考虑**加入一个元以区分信息**。 $=[x^{n+1}y^{n-k+1}]\sum\limits_{i=0}\dfrac{\big(xF(x)y\big)^i}{1-F(x)}$ $=[x^{n+1}y^{n-k+1}]\dfrac{1}{1-F(x)}\dfrac{1}{1-xF(x)y}$ 注意到右边是与 $k$ 无关的,我们只需要求出 $[x^{n+1}]$ 项关于 $y$ 的多项式就得到了答案。 考虑使用拉格朗日反演,多项式复合系列理论。 但注意到 $\dfrac{1}{1-F(x)}\dfrac{1}{1-xF(x)y}$ 除了 $F(x)$ 还有额外的 $x$ ,不容易用经典的多项式复合直接描述。 先设$W(x)=xF(x)$ , $H(x)$ 满足 $\dfrac{W(x)}{H(W(x))}=x$, 即 $\dfrac{xF(x)}{H(W(x))}=x$ , 则 $F(x)=H(W(x))$ (我们使用 $H(x)$ 给 $xF(x)$ 消去了 $x$ ) 又设 $G(x)=\dfrac{1}{1-H(x)}\dfrac{1}{1-xy}$ - **注** : 手动爆算可求得 $G'(x)=\dfrac{y+H'(x)-yH(x)-xyH'(x)}{\big(1-H(x)\big)^2\big(1-xy\big)^2}$ 那么有 $G(W(x))=\dfrac{1}{1-H(W(x))}\dfrac{1}{1-W(x)y}=\dfrac{1}{1-F(x)}\dfrac{1}{1-xF(x)y}$ (现在我们以经典多项式复合描述了问题) 现在的问题就是 $[x^{n+1}]G(W(x))$。 设 $P(x)$ 为 $W(x)$ 的复合逆 ,不难发现 $P(x)=\dfrac{x}{H(x)}$ , 则 $\dfrac{P(x)}{x}=\dfrac{1}{H(x)}$。 使用**扩展拉格朗日反演**可得 $$[x^{n+1}]H(W(x))=\dfrac{1}{n+1}[x^n]\dfrac{y+H'(x)-yH(x)-xyH'(x)}{\big(1-H(x)\big)^2\big(1-xy\big)^2}\left(\dfrac{P(x)}{x}\right)^{-(n+1)}$$ 忽略常数 $\frac{1}{n+1}$ 并代换 $$=[x^n]\dfrac{y\big(1-H(x)\big)+\big(1-xy\big)H'(x)}{\big(1-H(x)\big)^2\big(1-xy\big)^2}H(x)^{n+1}$$ $$=[x^n]\left(\dfrac{y}{\big(1-H(x)\big)\big(1-xy\big)^2}+\dfrac{H'(x)}{\big(1-H(x)\big)^2\big(1-xy\big)}\right)H(x)^{n+1}$$ 展开关于 $y$ 的封闭形式 $$=[x^n]\left(\dfrac{1}{\big(1-H(x)\big)}\sum\limits_{i=0}(i+1)x^iy^{i+1}+\dfrac{H'(x)}{\big(1-H(x)\big)^2}\sum\limits_{i=0}(xy)^i\right)H(x)^{n+1}$$ 考虑提取 $[y^m]$ 系数 $$[x^ny^m]\left(\dfrac{1}{\big(1-H(x)\big)}\sum\limits_{i=0}(i+1)x^iy^{i+1}+\dfrac{H'(x)}{\big(1-H(x)\big)^2}\sum\limits_{i=0}(xy)^i\right)H(x)^{n+1}$$ $$=[x^n]\left(\dfrac{mx^{m-1}}{\big(1-H(x)\big)}+\dfrac{H'(x)x^m}{\big(1-H(x)\big)^2}\right)H(x)^{n+1}$$ $$=[x^{n-m+1}]m\dfrac{H(x)^{n+1}}{\big(1-H(x)\big)}+[x^{n-m}]\dfrac{H'(x)H(x)^{n+1}}{\big(1-H(x)\big)^2}$$ 分别求出 $\dfrac{H(x)^{n+1}}{\big(1-H(x)\big)}$ 和 $\dfrac{H'(x)H(x)^{n+1}}{\big(1-H(x)\big)^2}$ 即可,问题变为了求 $H(x)$。 常规的多项式复合逆肯定是不行的,注意到问题的特殊性 : $W(x)=xF(x)=e^x-1$ 容易构造 $\ln(e^x-1+1)=x$ , 那么有 $P(x)=\ln(x+1)$ ,则 $H(x)=\dfrac{x}{\ln(x+1)}$。 ( 此处有 $1-H(x)$ 的常数项为 $0$ ,仍需要分式域,记得偏移次数 ) 综上,我们以 $O(n\log n)$ 或者 $O(n\log^2n)$ 的复杂度解决了本题。 实现中使用了大力倍增快速幂,时限比较松就卡过去了。 ```cpp #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; } ```