落谷P3041 [USACO12JAN]Video Game G

题目链接


多模式匹配问题,先建 AC 自动机。

套路性的搞个 DP:

  • \(f[i][j]\) 表示前 \(i\) 个字符,当前在 \(AC\) 自动机上的节点是 \(j\) 能匹配到的最多分。
  • 初始化 \(f[0][0] = 0\),其余为负无穷
  • 答案 \(\max\{f[K][i]\}\)

考虑一条边 \(u \Rightarrow v\),加入后缀的贡献,即 \(f[i][v] = \max(f[i - 1][u] + val(v))\)

其中 \(val(v)\) 表示 \(v\) 这个点上出现的模式串个数,即其 fail 树的他到根的链上的是模式串末尾的个数。

复杂度 \(O(45NK)\)

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int S = 20, L = 305, M = 1005;

int n, K, tr[L][3], fail[L];
int cnt[L], q[L], idx, f[M][L];
char s[S];

void insert() {
	int p = 0;
	for (int i = 1; s[i]; i++) {
		int ch = s[i] - 'A';
		if (!tr[p][ch]) tr[p][ch] = ++idx;
		p = tr[p][ch];
	}
	cnt[p]++;
}

void build() {
	int hh = 0, tt = -1;
	for (int i = 0; i < 3; i++)
		if (tr[0][i]) q[++tt] = tr[0][i];
	while (hh <= tt) {
		int u = q[hh++];
		for (int i = 0; i < 3; i++) {
			int v = tr[u][i];
			if (v) {
				fail[v] = tr[fail[u]][i];
				cnt[v] += cnt[fail[v]];
				q[++tt] = v;
			} else tr[u][i] = tr[fail[u]][i];
		}
	}
}

int main() {
	memset(f, 0xcf, sizeof f);
	scanf("%d%d", &n, &K);
	for (int i = 1; i <= n; i++) {
		scanf("%s", s + 1);
		insert();
	}
	build();
	f[0][0] = 0;
	for (int i = 0; i < K; i++) {
		for (int u = 0; u <= idx; u++) {
			if (f[i][u] < 0) continue;
			for (int j = 0; j < 3; j++) {
				int v = tr[u][j];
				f[i + 1][v] = max(f[i + 1][v], f[i][u] + cnt[v]);
			}
		}
	}
	int ans = 0;
	for (int i = 0; i <= idx; i++) ans = max(ans, f[K][i]);
	printf("%d\n", ans);
}
posted @ 2020-03-08 20:36  DMoRanSky  阅读(160)  评论(0编辑  收藏  举报