默认大家都学过trie与AC自动机。
先求出fail,对于每个节点维护一个sum,sum[u]待表从根到u所形成的字符串能拿到几分。显然sum[u]=sum[fail] + (u是几个字符串的结尾)。
设dp[i][j]代表长度为i到trie树上的j号节点所得的最大分数,显然有dp[i+1][j的字符k儿子] = max{dp[i+1][j的字符k儿子], dp[i][j] + sum[j的字符k儿子]}
memset(dp, -1, sizeof(dp)); dp[0][1] = 0; for (int i = 1; i <= k; i++) { for (int j = 1; j <= tot; j++) { if (dp[i - 1][j] == -1) continue; for (int l = 0; l < 26; l++) { dp[i][trie[j][l]] = max(dp[i][trie[j][l]], dp[i - 1][j] + sum[trie[j][l]]); } } }
然后答案就是max{dp[k][i]},i是所有trie树上的节点。
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using namespace std; int n, k; char s[1000]; int tu[1000][26]; int trie[1000][26], tot = 1; int fail[1000], sum[1000]; int dp[1010][1000]; queue<int> q; int main() { scanf("%d%d", &n, &k); for (int i = 1, len; i <= n; i++) { scanf("%s", s + 1); len = strlen(s + 1); int p = 1; for (int j = 1; j <= len; j++) { int m = s[j] - 'A'; if (!trie[p][m]) trie[p][m] = ++tot; p = trie[p][m]; } sum[p]++; } for (int i = 0; i < 26; i++) trie[0][i] = 1; fail[1] = 0; q.push(1); while (!q.empty()) { int p = q.front(); q.pop(); for (int i = 0; i < 26; i++) { if (trie[p][i]) { fail[trie[p][i]] = trie[fail[p]][i]; sum[trie[p][i]] += sum[fail[trie[p][i]]]; q.push(trie[p][i]); } else { trie[p][i] = trie[fail[p]][i]; } } } memset(dp, -1, sizeof(dp)); dp[0][1] = 0; for (int i = 1; i <= k; i++) { for (int j = 1; j <= tot; j++) { if (dp[i - 1][j] == -1) continue; for (int l = 0; l < 26; l++) { dp[i][trie[j][l]] = max(dp[i][trie[j][l]], dp[i - 1][j] + sum[trie[j][l]]); } } } int ans = -1; for (int i = 1; i <= tot; i++) { ans = max(ans, dp[k][i]); } cout << ans; return 0; }