2021CCPC網路賽(重賽)F. Nun Heh Heh Aaaaaaaaaaa(DP、簡單數論)
阿新 • • 發佈:2021-10-14
-
題意:給出一個串s,求出s的子序列中滿足字首為"nunhehheh",字尾為若干個(至少一個)'a'(不能包含其他的字元)組成的串的個數,該串僅僅由字首和字尾組成。例如:"nunhehheha","nunhehhehaaaa"滿足條件,而"nunhehhehba","nunhehheh"則不滿足條件。
-
思路:假設t = "nunhehheh",s和t的長度為n和m,首先預處理(i ~ n)a的個數(字尾和),接著利用子序列匹配的dp進行匹配,最後遍歷一遍s,當出現'h'時候可能字首的個數進行更新,則通過字首個數 * a的貢獻(\(2^x\)
-
解析:
- \(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 $,這樣就能保證不出現負值。
- \(f_{i, j}\)表示s的前i個字元的子序列中與t的前j個字元相同的個數,dp轉移方程如下:
-
程式碼:
#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; }