题意:你被给予了一个整数值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)子串,那么我们就可以进行一次转移,转移方程是dp[i + 1][q] = min(dp[i + 1][q], dp[i][j]);
#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
", res);
return 0;
}