[解題記錄] NOI Online 2022 入門組 字串
阿新 • • 發佈:2022-03-31
NOI Online 2022 入門組 字串
題意簡述
給定兩個字串 \(S\) 和 \(T\) 和一個初始為空的字串 \(R\),其中 \(S\) 長度為 \(n\),且只由 0
, 1
, -
三種字元構成(注:這裡的第三種字元是減號), 然後進行 \(n\) 次操作, 每次會取出 \(S\) 的第一個字元(記為 \(c\)),並將其從 \(S\) 中刪去。如果 \(c = \texttt{-}\),則要刪去 \(R\) 的開頭字元或結尾字元(資料保證刪去後 \(R\) 不為空)。將 \(c\) 加入到 \(R\) 的末尾。問將 \(R\) 變成 \(T\) 的方案數。
解題思路
首先不難想到一個 \(\mathcal O(2^{n/2})\)
看到題目的資料範圍,以及題目要我們求方案數,就聯想到 DP, 我們定義狀態 \(f[i][j][x][y]\) 表示 \(S\) 操作到第 \(i\) 個, 成功匹配了 \(T\) 的前 \(j\) 個數, \(R\) 前面要刪除 \(x\) 個數,後面要刪除 \(y\) 個數的方案,那麼狀態轉移方程就是 :
- 若 \(S_i=-\), \(f[i + 1][j][x - 1][y] += f[i][j][x][y]\)
- 若 \(S_i \ne -\), \(f[i + 1][j][x][l+1] += f[i][j][x][y]\)
- 若 \(S_i \ne -\) 且 \(S_i = T_{j - 1}\) 且 \(l = 0\), \(f[i+1][j +1][x][y] += f[i][j][x][y]\)
複雜度是 \(\mathcal O(n ^ 3m)\) 的,不能通過所有資料,但是我們發現其實最後一維完全沒有必要記錄,因為每次操作完後,\(R\) 的字元數是確定的,所以只要知道前面有幾個要刪除,成功匹配了幾個,狀態就是唯一確定的,去掉一維後複雜度就變成 \(\mathcal O (n ^ 2m)\) 的,但是實現一下就會發現 DP 要處理的細節非常多,我們所以可以寫 記憶化搜尋 !
Code
#include <bits/stdc++.h> using namespace std; const int N = 410, mod = 1e9 + 7; int f[N][N][N], n, m, T; char s[N], t[N]; int dfs(int i, int j, int x, int y) {//表示操作到s[i],匹配成功j個,前面要刪x個,後面刪除y個走下去成功的方案 if (i > n) return j == m && !x && !y; if (~f[i][j][x]) return f[i][j][x]; int val = 0; if (s[i] == '-') { if (x) val = (val + dfs(i + 1, j, x - 1, y)) % mod;//刪前面的 if (y) val = (val + dfs(i + 1, j, x, y - 1)) % mod;//刪後面的 } else { val = (val + dfs(i + 1, j, x, y + 1)) % mod;//直接放到後面 if (s[i] == t[j + 1] && !y) val = (val + dfs(i + 1, j + 1, x, y)) % mod;//如果匹配成功 if (!j && !y) val = (val + dfs(i + 1, j, x + 1, y)) % mod;//如果中間和後面都是空的 } return f[i][j][x] = val; } int main() { scanf("%d", &T); while (T--) { memset(f, -1, sizeof(f)); scanf("%d%d%s%s", &n, &m, s + 1, t + 1); printf("%d\n", dfs(1, 0, 0, 0)); } return 0; }