【bzoj5130】[Lydsy12月賽]字符串的週期 DFS+KMP

題目描述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;
}
相關文章
相關標籤/搜索