P5417 [CTSC2016]萨菲克斯·阿瑞 解题报告
P5417 [CTSC2016]萨菲克斯·阿瑞 解题报告:
3000 AC。
题意
计数长度为 \(n\),字符集大小为 \(m\),且每个字符出现次数不超过 \(c_i\) 的 SA 数量。
\(1\leqslant n,m\leqslant 500\)。
分析
直接对 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\) 就是等号的数量)
考虑 dp 容斥系数和(这个歌过程类似于不等式链的判定),令 \(f_{r,i,j}\) 表示只塞入 \(1\) 到 \(r\) 的字母,放了 \(i\) 个位置进去,目前的段长度为 \(k\) 的容斥系数和。
转移:
- 用字母 \(r\) 填满这一段,然后分段:$$f_{r+1,i+k,0}\leftarrow \frac{1}{(j+k)!}\cdot f_{r,i,j}$$
- 把字母 \(r\) 填完:$$f_{r,i+c_r,j+c_r}\leftarrow f_{r,i,j}$$
- 钦定一个等号:$$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\) 的字母去掉。
#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;
}