题目描述spa
给定 $n$ 和 $m$ ,求全部 长度为 $n$ ,字符集大小为 $m$ 的字符串,每一个前缀的最短循环节长度乘积 的总和。code
$n\le 12,m\le 10^9$blog
题解字符串
DFS+KMPio
对于字符串中的每一种字符,将其看做:该字符第一次出现位置以前的字符种类数+1,把获得的序列称为“该字符串的最小表示”。class
那么显然本题中最小表示相同的字符串的答案是同样的。$n$ 很小,所以能够暴搜最小表示序列,而后计算贡献,乘以最小表示为这个序列的字符串个数统计到答案中。循环
统计贡献时使用到KMP的一个小结论:长度为 $n$ 的字符串最短循环节长度为 $n-next[n]$ 。所以求出 $next[]$ ,对每一个前缀算一下乘起来。程序
最小表示为该序列的字符串个数是一个排列数,为 $A_{m}^{字符种类数}$ ,预处理排列计算便可。统计
通过一个小dp程序能够计算出 $n=12$ 时最小表示个数为4213597,所以时间复杂度为 $O(4213597·12)$ next
#include <cstdio> #define mod 998244353 typedef long long ll; int s[13] , next[13] , n , m; ll a[13] , ans; void dfs(int p , int v) { int i; if(p == n) { int j; ll ret = 1; for(j = -1 , i = 1 ; i <= n ; i ++ ) { while(~j && s[j] != s[i - 1]) j = next[j]; next[i] = ++j , ret = (ret * (i - j)) % mod; } ans = (ans + ret * a[v]) % mod; return; } for(i = 1 ; i <= v ; i ++ ) s[p] = i , dfs(p + 1 , v); if(v + 1 <= m) s[p] = v + 1 , dfs(p + 1 , v + 1); } int main() { int i; scanf("%d%d" , &n , &m); a[0] = 1; for(i = 1 ; i <= n && i <= m ; i ++ ) a[i] = a[i - 1] * (m - i + 1) % mod; next[0] = -1 , dfs(0 , 0); printf("%lld\n" , ans); return 0; }