大连的现场赛啊,快过去一年了。赛后知道这题是“AC自动机”的题目后就决定要研究研究这个神秘的AC自动机,最近把它给研究了一下,就把这个题翻出来再做做。发现还不是简单的AC自动机,还结合了“状态压缩dp”。好题,好题……
这次比赛居然有3道dp,悲剧的我们一道都木有想出来...
还有两个dp是:The Last Puzzle(C题), Number String(E题)
题意:输入n(n<=10)个基因片段,且每个基因片段有一个value(|value|<=100)。
问一个长度为L(L<=100)的基因可能的最大value值(多次出现的基因片段的value值不累加)。
样例:
3 8
ATG 4
TGC -2
ACCG 3
7
题解:由于最多只有10个基因片段,最多有2^10=1024种可能,所以可以结合AC自动机进行状态压缩dp.
dp[i][j][k]表示当前长度为i,字符串结尾状态为j,压缩状态为k.
压缩状态k看做二进制形式,第t位代表第t个基因片段是否被使用,
dp[i][j][k]是bool类型的,为true时表示该种情况可能存在。
最后在dp[L]中找value值最大的状态即可。
#include <cstdio> #include <iostream> #include <vector> #include <queue> #include <cstring> #include <algorithm> using namespace std; class ACAutomaton { public: static const int MAX_N = 100 * 10 + 5; //最大结点数:模式串个数 X 模式串最大长度 static const int CLD_NUM = 4; //从每个结点出发的最多边数,字符集Σ的大小,一般是26个字母 int n; //trie树当前结点总数 int id['z'+1]; //字母x对应的结点编号为id[x] int fail[MAX_N]; //fail指针 int tag[MAX_N]; //本题中表示 int trie[MAX_N][CLD_NUM]; //trie树,也就是goto函数 void init() { id['A'] = 0; id['T'] = 1; id['C'] = 2; id['G'] = 3; } void reset() { memset(trie[0], -1, sizeof(trie[0])); tag[0] = 0; n = 1; } void add(char *s, int v) { int p = 0; while (*s) { int i = id[*s]; if ( -1 == trie[p][i] ) { memset(trie[n], -1, sizeof(trie[n])); tag[n] = 0; trie[p][i] = n++; } p = trie[p][i]; s++; } tag[p] |= v; //因题而异 } void construct() { queue<int> Q; fail[0] = 0; for (int i = 0; i < CLD_NUM; i++) { if ( -1 != trie[0][i] ) { fail[trie[0][i]] = 0; //根结点下的第一层结点的fail指针都指向根结点 Q.push( trie[0][i] ); } else trie[0][i] = 0; //这个是阶段一中的第2步 } while ( !Q.empty() ) { int u = Q.front(); Q.pop(); for (int i = 0; i < CLD_NUM; i++) { int &v = trie[u][i]; if ( -1 != v ) { Q.push( v ); fail[v] = trie[fail[u]][i]; tag[v] |= tag[fail[v]]; //这个不能丢 } else v = trie[fail[u]][i]; } } } }ac; int n, l, value[11]; bool dp[2][ACAutomaton::MAX_N][1<<10]; inline int getValue(int state) { int sum = 0; for (int i = 0; i < n; i++) if ( state & (1 << i) ) sum += value[i]; return sum; } int main() { char gene[125]; ac.init(); while (cin >> n >> l) { ac.reset(); for (int i = 0; i < n; i++) { scanf("%s %d", gene, &value[i]); ac.add(gene, 1 << i); } ac.construct(); int nState = 1 << n; int pre = 1, cur = 0; memset(dp[cur], 0, sizeof(dp[cur])); dp[cur][0][0] = true; for (int i = 0; i < l; i++) { swap(pre, cur); memset(dp[cur], 0, sizeof(dp[cur])); for (int j = 0; j < ac.n; j++) { for (int k = 0; k < 4; k++) { int cld = ac.trie[j][k]; for (int t = 0; t < nState; t++) { if (dp[pre][j][t]) dp[cur][cld][t|ac.tag[cld]] = true; //状态转移方程 } } } } int ans = -1; for (int k = 0; k < nState; k++) for (int j = 0; j < ac.n; j++) if (dp[cur][j][k]) { ans = max(ans, getValue(k)); break; //第二重循环只用计算一次,因为每次都是计算的状态k,都一样 } if (ans < 0) printf("No Rabbit after 2012!\n"); else printf("%d\n", ans); } return 0; }