题目链接: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; }