大连的现场赛啊,快过去一年了。赛后知道这题是“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;
}