1. 程式人生 > 其它 >2021CCPC網路賽(重賽)F. Nun Heh Heh Aaaaaaaaaaa(DP、簡單數論)

2021CCPC網路賽(重賽)F. Nun Heh Heh Aaaaaaaaaaa(DP、簡單數論)

  • 題目:Nun Heh Heh Aaaaaaaaaaa

  • 題意:給出一個串s,求出s的子序列中滿足字首為"nunhehheh",字尾為若干個(至少一個)'a'(不能包含其他的字元)組成的串的個數,該串僅僅由字首和字尾組成。例如:"nunhehheha","nunhehhehaaaa"滿足條件,而"nunhehhehba","nunhehheh"則不滿足條件。

  • 思路:假設t = "nunhehheh",s和t的長度為n和m,首先預處理(i ~ n)a的個數(字尾和),接著利用子序列匹配的dp進行匹配,最後遍歷一遍s,當出現'h'時候可能字首的個數進行更新,則通過字首個數 * a的貢獻(\(2^x\)

    (a的個數) - 1)即可。

  • 解析:

    • \(f_{i, j}\)表示s的前i個字元的子序列中與t的前j個字元相同的個數,dp轉移方程如下:
      • \(f_{i,0} = 1\);
      • \(s_i == t_j\), \(f_{i, j} = f_{i-1, j-1} + f_{i - 1, j}\)\(s_i\)\(t_j\)進行匹配,也可以不選擇其進行匹配);
      • \(s_i \neq t_j\), \(f_{i, j} = f_{i-1, j}\)\(s_i與t_j\)無法進行匹配)。
    • s子序列中更新一次"nunhehheh"的個數則可以計算一次其之後所有a的貢獻,即\(C_x^1 + C_x^2 + .. + C_x^x = 2^x - 1\)
    • 因為最後結果需要取模,所以\(f_{i, j}\)的值不一定是遞增的,可能取模後比之前的值小,那麼\(f_{i,j}\)在與上一次計算字首個數發生變化的值\(f_{x, y}\)相減時可能出現負值,所以這裡可以利用取模的同餘性質:$x % mod = (x % mod + mod) % mod $,這樣就能保證不出現負值。
  • 程式碼:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const int N = 1e5 + 5;
    const int M = 15;
    const ll mod = 998244353;
    int T;
    ll f[N][M]; //s到第i位, t到第j位, s的子序列與t相同的個數
    ll num[N]; //從n->i a的個數
    
    ll qpow(ll n) //2^n
    {
        ll ans = 1, a = (ll)2;
        while(n)
        {
            if(n & 1) ans = ans * a % mod;
            a = a * a % mod;
            n >>= 1;
        }
        return ans % mod;
    }
    
    int main()
    {
        cin >> T;
        while(T --)
        {
            string s, t = "nunhehheh";
            memset(f, 0, sizeof f);
            memset(num, 0, sizeof num);
            cin >> s;
            int n = s.length(), m = t.length();
            s = " " + s, t = " " + t;
            for(int i = n; i >= 1; i--)
            {
                if(s[i] == 'a') num[i] = num[i + 1] + 1;
                else num[i] = num[i + 1];
            }
            for(int i = 0; i <= n; i++) f[i][0] = 1;
            for(int i = 1; i <= n; i++)
            {
                for(int j = 1; j <= m; j++)
                {
                    if(j > i) continue;
                    if(s[i] == t[j])
                        f[i][j] = (f[i - 1][j - 1] + f[i - 1][j]) % mod;
                    else f[i][j] = f[i - 1][j] % mod;
                }
            }
            ll res = 0;
            ll lastVal = 0; //上一次s序列中的子序列為t的個數
            for(int i = 1; i < n; i++)
            {
                if(s[i] == 'h')
                {
                    res = ( (res % mod) + ( (f[i][m] - lastVal + mod) % mod * (qpow( num[i + 1]) - 1) % mod ) % mod ) % mod;
                    lastVal = f[i][m];
                }
            }
            cout << res % mod << endl;
        }
        return 0;
    }