F. x-prime Substrings(AC自動機 + dp)
阿新 • • 發佈:2020-08-28
題意:你被給予了一個整數值x還有一個由1~9的數字組成的字串。
讓我們定義\(f(l,r)\)為\(s[l...r]\)之間的數字和。
讓我們稱一個子串\(s[l_{1}...r_{1}]\)為\(x-prime\)的,如果
\(f(l_{1}, r_{1}) = x\)
不存在值\(l_{2}, r_{2}\)使得
\(l_{1} <= l_{2} <= r_{2} <= l_{1}\)
\(f(l_{2}, r_{2}) != x\)
\(x被f(l_{2}, r_{2})整除\)
你可以擦除這個字串中的一些字元。如果你擦除了一個字元,那麼剩餘兩部分則會合併成一個字串。
求最小的擦除數量,使得這個字串不包含\(x-prime\)
分析:ac自動機dp。我們可以暴搜搜出所有不合法的子串(最多有2500個子串,每個字元的最長長度為20),然後建立成一棵trie樹。我們可以定義如下的狀態dp[i][j]:考慮到當前字串的第i個字元,並且已經匹配到AC自動機的第j個節點的最小擦除數量。那麼有兩種決策,首先是擦除當前字元,那麼dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] + 1);即考慮到下個字元i + 1,依然匹配到第j個節點,那麼第i個字元是被擦除的,加上權值1。第二種操作是不擦除,那麼我們的AC自動機如果有一條出邊\(tr[i][j]指向一個節點q\),並且這個q沒有\(x-prime\)
#include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <algorithm> using namespace std; const int N = 1005; //最多的情況子串個數 const int M = 2500; char s[N]; char str[20]; int tr[M * 20][10]; int q[M * 20]; int x, idx; int ne[M * 20]; //考慮到當前第i個字元,走到ac自動機的第j個字元需要擦去的字元個數 int dp[N][20 * M]; bool match[M * 20]; void insert(int pos) { int p = 0; for (int i = 0; i < pos; ++i) { int t = str[i] - '0'; if (!tr[p][t]) tr[p][t] = ++idx; p = tr[p][t]; } match[p] = true; } void build() { int hh = 0, tt = -1; for (int i = 1; i <= 9; ++i) { if (tr[0][i]) q[++tt] = tr[0][i]; } while (hh <= tt) { int t = q[hh++]; for (int i = 1; i <= 9; ++i) { int p = tr[t][i]; if (!p) tr[t][i] = tr[ne[t]][i]; else { ne[p] = tr[ne[t]][i]; q[++tt] = p; } } } } bool check(char str[], int pos) { for (int i = 0; i < pos; ++i) { int sum = 0; for (int j = i; j < pos; ++j) { sum += str[j] - '0'; if (sum != x && x % sum == 0) return false; } } return true; } void dfs(char str[], int pos, int sum) { if (sum > x) return; if (sum == x) { if (check(str, pos)) { insert(pos); } } for (int d = 1; d <= 9 && sum + d <= x; ++d) { str[pos] = char(d + '0'); dfs(str, pos + 1, sum + d); } } int main() { scanf("%s", s + 1); int len = strlen(s + 1); scanf("%d", &x); dfs(str, 0, 0); build(); memset(dp, 0x3f, sizeof dp); dp[1][0] = 0; for (int i = 1; i <= len; ++i) { int c = s[i] - '0'; for (int j = 0; j <= idx; ++j) { int q = tr[j][c]; if (dp[i][j] != 0x3f3f3f3f) { //擦去字元 dp[i + 1][j] = min(dp[i + 1][j], dp[i][j] + 1); if (!match[q]) dp[i + 1][q] = min(dp[i + 1][q], dp[i][j]); } } } int res = 0x3f3f3f3f; for (int i = 0; i <= idx; ++i) res = min(res, dp[len + 1][i]); printf("%d\n", res); return 0; }