POJ 3691 安徽第二题 有限状态自动机+DP

http://acm.pku.edu.cn/JudgeOnline/problem?id=3691

题目大意是说给定一个长度最大为1000的仅由A,T,C,G四个字符组成的字符串,要求改变最少的字符个数,使得得到的字符串中不含有任意预先给定的一系列子串。

 一般都会想到用动态规划,但是怎么DP是个问题,怎么设计状态。换个角度,我们要最终的字符串不含有任意模板串,这个涉及到多串匹配的知识。我们知道,可以建立一个有限状态自动机DFA来判断一个字符串中是否还有某些模板串。关于多模板串的DFA的建立,可以利用字符串前缀函数特殊的性质,建立一个TRIE图(具体建立方法可以参见相关论文)。这样,给定一个字符串,就可以在这个TRIE图中沿相应的边转移,如果途经一个危险节点,也就是目标串中出现了一个模板串。所以,给定一个模板串,如果在TRIE图中“走”的过程中没有到达过危险节点,那么这个字符串就符合要求。

现在来看原题,要求把一个字符串改变最小的字符数,使得最终的字符串符合要求。由于模板串不变,DFA也没有变化,所以我们这样DP:设dp[i][j]表示到了目标串的第i个字符,并且走到了自动机的状态点j,中途没有经过任何危险结点,所改变的字符串的最小个数。具体DP过程也是比较简单的。直接看代码就差不多了。最后的答案就是min{dp[len][j]} (len是目标串的长度,j是自动机中的一个状态点,并且状态j不是目标状态)

#include <stdio.h>
#include 
<string.h>

const int maxn=1000;
const int maxnodes=480;
const int maxchar=4;

int child[maxnodes][maxchar];
bool danger[maxnodes];
int suffix[maxnodes],q[maxnodes];
int nodes,f,r,p;
int dp[maxn][maxnodes];
char str[maxn];

inline 
int getwordid(char c) {
    
switch(c) {
        
case 'A':return 0;
        
case 'G':return 1;
        
case 'C':return 2;
        
case 'T':return 3;
    }
}

bool build_trie() {
    
char words[30];
    
int m;
    nodes
=1;
    scanf(
"%d",&m);
    
if(m==0return false;
    memset(child,
0,sizeof(child));
    memset(danger,
0,sizeof(danger));
    memset(suffix,
0,sizeof(suffix));
    memset(q,
0,sizeof(q));
    
for(int i=0;i<m;i++) {
        scanf(
"%s",words);
        
int len=strlen(words);
        p
=1;
        
for(int j=0;j<len;j++) {
            
int d=getwordid(words[j]);
            
if(child[p][d]==0) {
                nodes
++;
                child[p][d]
=nodes;
            }
            p
=child[p][d];
            
if(danger[p]) break;
        }
        danger[p]
=true;
    }
    
return true;
}

void build_graph() {
    f
=r=0;
    
for(int i=0;i<maxchar;i++) {
        
if(child[1][i]==0) {
            child[
1][i]=1;
        }
else {
            r
++;
            q[r]
=child[1][i];
            suffix[child[
1][i]]=1;
        }
    }
    
while(f<r) {
        f
++;
        danger[q[f]]
=danger[q[f]] || danger[suffix[q[f]]];
        
if(!danger[q[f]]) {
            
for(int i=0;i<maxchar;i++) {
                
if(child[q[f]][i]==0
                    child[q[f]][i]
=child[suffix[q[f]]][i];
                
else {
                    r
++;
                    q[r]
=child[q[f]][i];
                    suffix[q[r]]
=child[suffix[q[f]]][i];
                }
            }
        }
    }
}

void checkmin(int& a,int b) {
    
if(a==-1) a=b;
    
else if(a>b) a=b;
}

int main() {
    
//freopen("in.txt","r",stdin);
    int cases=0;
    
while(build_trie()) {
        cases
++;
        printf(
"Case %d: ",cases);
        build_graph();
        scanf(
"%s",str);
        
int len=strlen(str);
        
        memset(dp,
-1,sizeof(dp));
        
if(!danger[child[1][getwordid(str[0])]])
            dp[
0][child[1][getwordid(str[0])]]=0;
        
for(int i=0;i<maxchar;i++)
            
if(getwordid(str[0])!=i&&!danger[child[1][i]])
                dp[
0][child[1][i]]=1;
        
for(int i=1;i<len;i++) {
            
for(int j=1;j<=nodes;j++) {
                
if(dp[i-1][j]!=-1) {
                    
if(!danger[child[j][getwordid(str[i])]])
                        checkmin(dp[i][child[j][getwordid(str[i])]],dp[i
-1][j]);                 
                    
for(int k=0;k<maxchar;k++)
                        
if(getwordid(str[i])!=k&&!danger[child[j][k]])
                            checkmin(dp[i][child[j][k]],dp[i
-1][j]+1);
                }
            }
        }
        
int ans=1<<20;
        
for(int i=1;i<=nodes;i++)
                
if(!danger[i]&&dp[len-1][i]!=-1&&dp[len-1][i]<ans)
                    ans
=dp[len-1][i];
        
if(ans==1<<20) ans=-1;
        printf(
"%d\n",ans);
    }
    
return 0;
}

 


posted on 2008-10-03 14:56  woodfish  阅读(3000)  评论(8编辑  收藏  举报

导航