题目意思
有n个字符串,每个字符串都有对应的权值,那么我们要组建一个长度为m的字符串,并且要是这个字符串的权值最大,如果最大权值小于零,输出"No Rabbit after 2012!",否则输出最大的权值
解决思路
这里我们选择相应的字符串,要在Trie树上选择,并且我们还要进行模式串的匹配(匹配最大的数值),那么我们就一定会用到AC自动机,并且我们这里要确定一定长度的字符串使得权值最大,那么我们就会用到状压DP
这里我们用到的状态压缩就是表示那个字符串被选到了,哪个字符串没有被选到,选到用1表示,没有选到用0表示,那么拥有的状态总数就是:(1<< n)-1
首先是我们的AC自动机和我们平时的有什么区别,平时我们是在字符串的最后一个字符位置的val数组进行加1操作,但是这个中这样做就没有意义了,因为我们这里要用状态表示哪一个关键词被选择哪一个没有被选择,那么我们这里应该储存的是(1<<i)(i表示的是第几个输入的字符串)
然后在我们构建失配指针,如果我们想最原始的构建方法的话,我们找到一个节点是关键词的末尾节点的话,我们还需要向上进行回溯,一直到根节点,要把这个路径中经过的所有涵盖关键词的字符串全部找出来,或者我们像《AC自动机模板》中最后一个模板中一样,我们添加一个last数组,使得每次last中回溯的都是字符串的末尾适配节点,但是这样我们还是需要回溯,有没有一种不需要回溯的方法呢?答案是有的,这里就得益于我们的状态压缩,因为每个字符串的状态都是不一样的,我们只需要对他的失配节点进行 '|' 操作,就可以得到到达这个节点,所有涵盖的字符串信息!!实质上他的意思就是:如果到这里已经是关键字符串了,那么是他子串的关键字符串也一定存在在这里面
这样我们的AC自动机部分就可以是完成了
最后是我们的状压DP部分dp[i][j][state]:字符串长度为i,现在在树中的节点编号为j,并且字符串的状态为state时,是否可行!
状态转移方程:if(dp[i][j][state] == true) dp[ i+1 ] [ ch[j][k] ][ state | val[ ch[j][k] ] ] (k表示编号为j的第k个子节点)
我们可以想到:i+1长度的字符串信息时之和长度为i的字符串信息有关,那么我们这里就可以想到滚动数组了!
程序实现
#include <iostream>
#include <cstdio>
#include <cstring>
#define INF 0x3f3f3f3f
using namespace std;
const int N = 11;
const int MAXN = 1010;
int n,m;
int ch[MAXN][N],sz,fail[MAXN],val[MAXN];
int v[N];
void Init()
{
memset(fail,0,sizeof(fail));
memset(ch[0],0,sizeof(ch[0]));
sz = 1;val[0] = 0;
}
int idx(char x)
{
if(x == 'A') return 0;
else if(x == 'T') return 1;
else if(x == 'G') return 2;
return 3;
}
void Insert(char *s,int x)
{
int u = 0,len = strlen(s);
for(int i = 0;i < len;i ++)
{
int c = idx(s[i]);
if(!ch[u][c])
{
memset(ch[sz],0,sizeof(ch[sz]));
val[sz] = 0;
ch[u][c] = sz++;
}
u = ch[u][c];
}
val[u] = 1<<x;//这里将每个字符串的存在状态存放在val数组中
}
void Get_fail()
{
int que[MAXN]; //数组模拟队列,更快一些
int l = 0,r = 0;
que[r++] = 0;
while(l < r)
{
int u = que[l++];
for(int i =0 ;i < 4;i ++)
{
int v = ch[u][i];
if(!v)
ch[u][i] = ch[fail[u]][i];
else
{
que[r++] = v;
if(u)//因为我们这里fail指针开始初始化都为0,所以这里就不需要考虑根节点直接连接的点,之需要考虑父亲节点不是根节点的点
fail[v] = ch[fail[u]][i];
val[v] |= val[fail[v]]; //将前面所有的字符串状态更新到当前节点
}
}
}
}
int Get_num(int num)//计算状态num下,权值大小
{
int ans = 0;
for(int i =0 ;i < n;i ++)
if(num & (1<<i))
ans += v[i];
return ans;
}
bool dp[2][MAXN][1<<N];
void Solve()
{
memset(dp,0,sizeof(dp));
dp[0][0][0] = 1;//初始化状态,很重要!!!!
for(int i = 1;i <= m;i ++)//枚举长度
{
memset(dp[i&1],0,sizeof(dp[i&1]));//滚动数组
for(int j = 0;j < sz;j ++)//父亲节点枚举
for(int k = 0;k < 4;k ++)//儿子节点枚举
for(int hh = 0; hh < (1<<n); hh ++) //所有的状态枚举
if(dp[(i+1)&1][j][hh])
dp[i&1][ ch[j][k] ][ hh|val[ch[j][k]] ] = 1;
}
int ans = -1*INF;
for(int j = 0;j < sz;j ++)
for(int k = 0;k < (1<<n);k ++)
if(dp[m&1][j][k])
ans = max(ans,Get_num(k));
if(ans >= 0) printf("%d\n",ans);
else printf("No Rabbit after 2012!\n");
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
Init();
char s[105];
for(int i = 0;i < n;i ++)
{
scanf("%s%d",s,&v[i]);
Insert(s,i);
}
Get_fail();
Solve();
}
}
参考博客
https://blog.csdn.net/martinue/article/details/50895963
https://www.cnblogs.com/kuangbin/p/3164106.html
后记
上面的描述会有点啰嗦......如果有错误,欢迎大家在下面评论区评论,谢谢!