题目链接:http://poj.org/problem?id=3691
题目大意:
给出一些能导致癌症的DNA序列,一个已知序列的DNA片段,问至少得改多少个碱基对才能使这个DNA片段不会致癌。
多字符串匹配的话,那首先想到的当然是AC自动机,不过这题中,AC自动机又和别的不一样,因为在这里的trie树中,每个节点只可能有四个儿子节点(A、T、G、C),建立自动机的时候,任一节点都不能有空,就是说当从结点通过一个不能往下连接的字符时,孩子要通过j的fail结点来找到,或者回到根。而一般的AC自动机是不用建立该指针,直接放空即可。
建立了AC自动机之后,我们得到如下的DP方程:
dp[i][son]=min(dp[i][son],dp[i-1][j]+(str[i-1]!=k))
dp[i][j]中,i代表匹配到DNA片段的第i个字符,j表示当前在trie树上的节点,son为j的儿子节点。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#define N 1002
using namespace std;
const int kind=4;
const int INF=0x3f3f3f;
int allocp;
int verID[100];
int n,dp[N][N];
char word[25],str[N];
struct TrieNode
{
int index;
bool unsafe;
TrieNode *next[kind];
TrieNode *fail;
}memory[N],*q[N];
TrieNode *createTrieNode()
{
TrieNode *p=&memory[allocp];
p->index=allocp++;
p->fail=NULL;
p->unsafe=false;
memset(p->next,0,sizeof(p->next));
return p;
}
void insertTrieNode(TrieNode *pRoot,char s[])
{
TrieNode *p=pRoot;
for(int i=0;s[i];i++)
{
int k=verID[s[i]];
if(p->next[k]==NULL)
p->next[k]=createTrieNode();
p=p->next[k];
}
p->unsafe=true;
}
void bulid_ac(TrieNode *pRoot)
{
int head=0,tail=0;
TrieNode *p,*temp;
q[tail++]=pRoot;
pRoot->fail=NULL;
while(head<tail)
{
p=q[head++];
for(int i=0;i<kind;i++)
{
if(p->next[i]!=NULL)
{
if(p==pRoot)
p->next[i]->fail=pRoot;
else
{
p->next[i]->fail=p->fail->next[i];
if(p->next[i]->fail->unsafe)
p->next[i]->unsafe=true;
}
q[tail++]=p->next[i];
}
else// 由于需要在安全节点上找,所以如果没有这个next也要
{
if(p==pRoot)
p->next[i]=pRoot;
else
p->next[i]=p->fail->next[i];
}
}
}
}
int main()
{
verID['A']=0,verID['T']=1,verID['G']=2,verID['C']=3;
int t=1;
TrieNode *pRoot;
while(~scanf("%d",&n),n)
{
allocp=0;
pRoot=createTrieNode();
while(n--)
{
scanf("%s",word);
insertTrieNode(pRoot,word);
}
bulid_ac(pRoot);
scanf("%s",str);
int len=strlen(str);
//DP部分,i表示当前匹配到第i位,j表示当前在trie树上的状态。
for(int i=0;i<=len;i++)
for(int j=0;j<allocp;j++)
dp[i][j]=INF;
dp[0][0]=0;
TrieNode *p;
for(int i=1;i<=len;i++)
{
for(int j=0;j<allocp;j++)
{
if(dp[i-1][j]!=INF)//之前更新过的才找
{
p=&memory[j];
for(int k=0;k<kind;k++)
{
if(!p->next[k]->unsafe)//只搜安全节点
{
int son=p->next[k]->index;
dp[i][son]=min(dp[i][son],dp[i-1][j]+(verID[str[i-1]]!=k));//如果不同需要+1,改变成该点。
}
}
}
}
}
int ans=INF;
for(int i=0;i<allocp;i++)
if(!memory[i].unsafe && ans>dp[len][i])
ans=dp[len][i];
printf("Case %d: %d\n",t++,ans==INF?-1:ans);
}
return 0;
}