题解 P5417【[CTSC2016]萨菲克斯·阿瑞】

· · 题解

P5417 [CTSC2016]萨菲克斯·阿瑞 解题报告:

更好的阅读体验

3000 AC。

题意

计数长度为 n,字符集大小为 m,且每个字符出现次数不超过 c_i 的 SA 数量。

## 分析 直接对 SA 的定义计数看上去就无从下手,考虑找一个性质转化。 考虑排序后相邻的两个后缀 $p_i,p_{i+1}$($p$ 就是 SA 数组),显然有 $s_{p_i}\leqslant s_{p_{i+1}}$。若取等,那么就要求 $p_i+1$ 对应的后缀在 SA 内的位置比 $p_{i+1}+1$ 对应的后缀前,否则没有要求。 可以发现上面那个性质也是充分的,即: **若 $p_i+1$ 在 SA 中的位置比 $p_{i+1}+1$ 前,那么 $s_{p_i}\leqslant s_{p_{i+1}}$,否则 $s_{p_i}<s_{p_{i+1}}$。** 可以将这些限制看成一个不等式链,我们现在想要对于每个合法的不等式链,计数其对应 SA 数量之和。 先考虑一个简单一些的问题,怎么判定一个不等式链合法: 用小于号将链分成若干段,每一段都贪心地填充,拿出最小的字符,能填完这个字符就填完,否则把这一段剩下的位置全部填满,然后丢掉这个字符。合法的条件就是能顺利进行这个过程。 然后考虑怎么计数: 小于号很烦人,于是我们容斥有哪些小于号变成了等于号,计算等于号和小于等于号的不等式链对应的 SA 数量。 手玩观察(?)可得方案数应为:按照没有改变的小于号分段,每一段的长度共同形成的多重组合数。 带上容斥系数,这个的贡献应该是这样的一个形式:($m-k$ 就是等号的数量) $$(-1)^{m-k}{n\choose l_1,l_2,\cdots,l_k}$$ 考虑 dp 容斥系数和(这个歌过程类似于不等式链的判定),令 $f_{r,i,j}$ 表示只塞入 $1$ 到 $r$ 的字母,放了 $i$ 个位置进去,目前的段长度为 $k$ 的容斥系数和。 转移: 1. 用字母 $r$ 填满这一段,然后分段:$$f_{r+1,i+k,0}\leftarrow \frac{1}{(j+k)!}\cdot f_{r,i,j}$$ 2. 把字母 $r$ 填完:$$f_{r,i+c_r,j+c_r}\leftarrow f_{r,i,j}$$ 3. 钦定一个等号:$$f_{r,i+k,j+k}\leftarrow (-1)\cdot f_{r,i,j}$$ 可以发现第二类转移和第三类转移能合并,$k$ 的范围从 $[1,c_r]$ 改为 $[1,c_r)$ 即可。 两类转移都可以用前缀和优化一下,于是复杂度就是 $O(n^2m)$ 了。 ## 代码 记得把 $c=0$ 的字母去掉。 ```cpp #include<stdio.h> const int maxn=505,mod=1000000007; int n,m,M,ans; int c[maxn],C[maxn],f[maxn][maxn],sum[maxn][maxn],fac[maxn],inv[maxn],nfac[maxn]; int main(){ scanf("%d%d",&n,&M); fac[0]=nfac[0]=1; for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=i==1? 1:(mod-1ll*(mod/i)*inv[mod%i]%mod),nfac[i]=1ll*nfac[i-1]*inv[i]%mod; for(int i=1;i<=M;i++){ scanf("%d",&C[i]); if(C[i]) c[++m]=C[i]; } f[0][0]=1; for(int r=1;r<=m;r++){ for(int i=0;i<=n;i++) for(int j=0;j<=i;j++) sum[i][j]=((i==0||j==0? 0:sum[i-1][j-1])+f[i][j])%mod,f[i][j]=0; for(int i=1;i<=n;i++) for(int j=1;j<=i;j++){ f[i][0]=(f[i][0]+1ll*nfac[j]*(sum[i-1][j-1]-(j<=c[r]? 0:sum[i-c[r]-1][j-c[r]-1])+mod))%mod; f[i][j]=(0ll+f[i][j]-(sum[i-1][j-1]-(j<c[r]? 0:sum[i-(c[r]-1)-1][j-(c[r]-1)-1]))+mod)%mod; } ans=(ans+f[n][0])%mod; } printf("%d\n",1ll*ans*fac[n]%mod); return 0; } ```