1. 程式人生 > 其它 >[解題記錄] NOI Online 2022 入門組 字串

[解題記錄] NOI Online 2022 入門組 字串

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})\)

的爆搜,就是列舉遇到減號時的決策,這樣可以拿 \(30pts\)

看到題目的資料範圍,以及題目要我們求方案數,就聯想到 DP, 我們定義狀態 \(f[i][j][x][y]\) 表示 \(S\) 操作到第 \(i\) 個, 成功匹配了 \(T\) 的前 \(j\) 個數, \(R\) 前面要刪除 \(x\) 個數,後面要刪除 \(y\) 個數的方案,那麼狀態轉移方程就是 :

  1. \(S_i=-\)\(f[i + 1][j][x - 1][y] += f[i][j][x][y]\)
  2. \(S_i \ne -\)\(f[i + 1][j][x][l+1] += f[i][j][x][y]\)
  3. \(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;
}