Live2d Test Env

【AC自动机&&Trie图】积累

以前KMP和后缀系列(主要是后缀数组,后缀自动机),都刷了一定数量的题,但是对于AC自动机,却有些冷落,罪过。

但是我感觉,在蓝桥杯比赛中AC自动机出现的概率比后缀系列大,简单的会考匹配,稍难一点会考AC自动机+DP ,AC自动机+矩阵乘法,或者套其他算法blabla...

 

Trie图是AC自动机的改良版,不需要一直向上找fail。然后这里整理了一下Trie图的模板。

 

HihoCoder1036:Trie图  (时间在hihocoder上面排第一)。

题意:问长字符串里是否出现过字典里的单词。
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000010;
char s[maxn];
int ch[maxn][26],num[maxn],Next[maxn];
int q[maxn],head,tail,cnt;
struct ACauto
{
    void insert()
    {
        int Now=0;
        for(int i=0;s[i];i++){
            if(!ch[Now][s[i]-'a'])  ch[Now][s[i]-'a']=++cnt;
            Now=ch[Now][s[i]-'a'];
        } num[Now]=1;
    }
    void build()
    {
        for(int i=0;i<26;i++){
            if(ch[0][i]){
                Next[ch[0][i]]=0;
                q[++head]=ch[0][i];
                //if(num[Next[ch[0][i]]]) num[ch[0][i]]=1;
            }
        }
        while(tail<head){
            int u=q[++tail];
            for(int i=0;i<26;i++){
                if(ch[u][i]){
                    q[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                    //if(num[Next[ch[u][i]]]) num[ch[u][i]]=1;
                }
                else ch[u][i]=ch[Next[u]][i];
            }
        }
    }
    bool find()
    {
        scanf("%s",s);
        int Now=0;
        for(int i=0;s[i];i++){
            Now=ch[Now][s[i]-'a'];
            if(num[Now]) return true;
        } return false;
    }
}Trie;
int main()
{
    int N,i,j;
    scanf("%d",&N);
    for(i=1;i<=N;i++){
        scanf("%s",s);
        Trie.insert();
     }
    Trie.build();
    if(Trie.find()) printf("YES\n");
    else printf("NO\n");
    return 0;
}
View Code

 

HDU2222 :Keywords Search (基础题型)

题意:问长字符串里出现了多少个字典里的单词。(字典里的单词可能重复,但是一个单词只统计一次)
思路:AC自动机统计以当前字母为后缀的单词数,并且沿fail指针一直统计,统计过后把num改为-1,避免重复统计。
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1000010;
char s[maxn];
int ch[maxn][26],num[maxn],Next[maxn];
int q[maxn],head,tail,cnt,ans;
struct ACauto
{
    void update()
    {
        head=tail=cnt=ans=0;
        memset(ch,0,sizeof(ch));
        memset(num,0,sizeof(num));
        memset(Next,0,sizeof(Next));
    }
    void insert()
    {
        int Now=0;
        for(int i=0;s[i];i++){
            if(!ch[Now][s[i]-'a'])  ch[Now][s[i]-'a']=++cnt;
            Now=ch[Now][s[i]-'a'];
        } num[Now]++;
    }
    void build()
    {
        for(int i=0;i<26;i++){
            if(ch[0][i]){
                Next[ch[0][i]]=0;
                q[++head]=ch[0][i];
            }
        }
        while(tail<head){
            int u=q[++tail];
            for(int i=0;i<26;i++){
                if(ch[u][i]){
                    q[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                }
                else ch[u][i]=ch[Next[u]][i];
            }
        }
    }
    void find()
    {
        scanf("%s",s);
        int Now=0;
        for(int i=0;s[i];i++){
            Now=ch[Now][s[i]-'a'];
            int tmp=Now;
            while(tmp&&num[tmp]!=-1){
                ans+=num[tmp]; num[tmp]=-1;
                tmp=Next[tmp];
            }
        }  return ;
    }
}Trie;
int main()
{
    int T,N,i,j;
    scanf("%d",&T);
    while(T--){
        Trie.update();
        scanf("%d",&N);
        for(i=1;i<=N;i++){
            scanf("%s",s);
            Trie.insert();
         }
        Trie.build();
        Trie.find();
        printf("%d\n",ans);
    }
    return 0;
}
View Code

 

HDU2896:病毒侵袭 (基础题型)

题意:给定N个病毒,M个网站,问对每个网站,带了哪些病毒,输出其编号。
思路:和上面差不多。注意memset会MTL。然后就是题目的ASCLL码范围的0到126。
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100010;
char s[maxn];
int ch[maxn][128],Next[maxn];
int q[maxn],num[maxn],head,tail,cnt,ans;
struct ACauto
{
    void update()
    {
        head=tail=cnt=ans=0;
        num[0]=Next[0]=0;
        for(int i=0;i<126;i++) ch[0][i]=0;
    }
    void insert(int opt)
    {
        int Now=0;
        for(int i=0;s[i];i++){
            if(!ch[Now][s[i]]){
                ch[Now][s[i]]=++cnt;
                for(int i=0;i<126;i++) ch[cnt][i]=0,num[cnt]=0,Next[cnt]=0;
            }
            Now=ch[Now][s[i]];
        } num[Now]=opt;
    }
    void build()
    {
        for(int i=0;i<128;i++){
            if(ch[0][i]){
                Next[ch[0][i]]=0;
                q[++head]=ch[0][i];
            }
        }
        while(tail<head){
            int u=q[++tail];
            for(int i=0;i<128;i++){
                if(ch[u][i]){
                    q[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                }
                else ch[u][i]=ch[Next[u]][i];
            }
        }
    }
    void find(int opt)
    {
        scanf("%s",s);
        int Now=0,x[10],size=0;
        for(int i=0;s[i];i++){
            Now=ch[Now][s[i]];
            int tmp=Now;
            while(tmp){
                if(num[tmp]) x[++size]=num[tmp];
                tmp=Next[tmp];
            }
        } 
        if(size){
            ans++; sort(x+1,x+size+1);
            printf("web %d: %d",opt,x[1]);
            for(int i=2;i<=size;i++) printf(" %d",x[i]); 
            printf("\n");
        } return ;
    }
}Trie;
int main()
{
    int N,Q,i,j;
    while(~scanf("%d",&N)){
        Trie.update();
        for(i=1;i<=N;i++){
            scanf("%s",s);
            Trie.insert(i);
         }
        Trie.build();
        scanf("%d",&Q);
        for(i=1;i<=Q;i++) Trie.find(i);
        printf("total: %d\n",ans);
    }
    return 0;
}
View Code

 但是上面那个需要一直沿fail指针上找,如果数据大一点就过不了。

改进是直接记录前缀‘和’,使不需要上找。

#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=100010;
char s[maxn];
int ch[maxn][128],Next[maxn];
int q[maxn],e[maxn][4],num[maxn],head,tail,cnt,ans;
struct ACauto
{
    void update()
    {
        head=tail=cnt=ans=0;
        num[0]=Next[0]=0;
        for(int i=0;i<126;i++) ch[0][i]=0;
    }
    void insert(int opt)
    {
        int Now=0;
        for(int i=0;s[i];i++){
            if(!ch[Now][s[i]]){
                ch[Now][s[i]]=++cnt;
                for(int i=0;i<126;i++) ch[cnt][i]=0,e[cnt][0]=0,Next[cnt]=0;
            }
            Now=ch[Now][s[i]];
        } e[Now][++e[Now][0]]=opt;
    }
    void build()
    {
        for(int i=0;i<128;i++){
            if(ch[0][i]){
                Next[ch[0][i]]=0;
                q[++head]=ch[0][i];
            }
        }
        while(tail<head){
            int u=q[++tail];
            for(int i=0;i<128;i++){
                if(ch[u][i]){
                    q[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                    for(int j=1;j<=e[ch[Next[u]][i]][0];j++){//记录前缀‘和 ’ 
                        e[ch[u][i]][++e[ch[u][i]][0]]=e[ch[Next[u]][i]][j];
                    }
                }
                else ch[u][i]=ch[Next[u]][i];
            }
        }
    }
    void find(int opt)
    {
        scanf("%s",s);
        int Now=0,x[4],size=0;
        for(int i=0;s[i];i++){
            Now=ch[Now][s[i]];
            int tmp=Now;
            for(int j=1;j<=e[Now][0];j++) x[++size]=e[Now][j];
            /*while(tmp){
                if(num[tmp]) x[++size]=num[tmp];
                tmp=Next[tmp];
            }*/
        } 
        if(size){
            ans++; sort(x+1,x+size+1);
            printf("web %d: %d",opt,x[1]);
            for(int i=2;i<=size;i++) printf(" %d",x[i]); 
            printf("\n");
        } return ;
    }
}Trie;
int main()
{
    int N,Q,i,j;
    while(~scanf("%d",&N)){
        Trie.update();
        for(i=1;i<=N;i++){
            scanf("%s",s);
            Trie.insert(i);
         }
        Trie.build();
        scanf("%d",&Q);
        for(i=1;i<=Q;i++) Trie.find(i);
        printf("total: %d\n",ans);
    }
    return 0;
}
View Code

 

HDU3065:病毒侵袭持续中  (基础题型)

题意:求每个单词在字符串里出现次数。
思路:和上一题差不多,记录路过单词的尾节点End的次数即可。字符串里不是大写字母的要回到根。
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=50010;
char s[1010][60],x[2000010];
int ch[maxn][26],Next[maxn];
int q[maxn],End[maxn],num[1010],head,tail,cnt,ans;
struct ACauto
{
    void update()
    {
        memset(num,0,sizeof(num));
        head=tail=cnt=ans=Next[0]=0;
        for(int i=0;i<26;i++) ch[0][i]=0;
    }
    void insert(int opt)
    {
        int Now=0;
        for(int i=0;s[opt][i];i++){
            if(!ch[Now][s[opt][i]-'A']){
                ch[Now][s[opt][i]-'A']=++cnt;
                Next[cnt]=End[cnt]=0;
                for(int i=0;i<26;i++) ch[cnt][i]=0;
            }
            Now=ch[Now][s[opt][i]-'A'];
        } End[Now]=opt;
    }
    void build()
    {
        for(int i=0;i<26;i++){
            if(ch[0][i]){
                Next[ch[0][i]]=0;
                q[++head]=ch[0][i];
            }
        }
        while(tail<head){
            int u=q[++tail];
            for(int i=0;i<26;i++){
                if(ch[u][i]){
                    q[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                }
                else ch[u][i]=ch[Next[u]][i];
            }
        }
    }
    void find()
    {
        scanf("%s",x); int Now=0;
        for(int i=0;x[i];i++){
            if(x[i]<'A'||x[i]>'Z') Now=0;
            else Now=ch[Now][x[i]-'A'];
            if(End[Now]) num[End[Now]]++;
        }
    }
}Trie;
int main()
{
    int N,Q,i,j;
    while(~scanf("%d",&N)){
        Trie.update();
        for(i=1;i<=N;i++){
            scanf("%s",s[i]);
            Trie.insert(i);
         }
        Trie.build();
        Trie.find();
        for(i=1;i<=N;i++)
         if(num[i]) {
            printf("%s: %d\n",s[i],num[i]);    
        }
    }
    return 0;
}
View Code

 

POJ2778: DNA Sequence (AC自动机+矩阵)

题意:给定几个病毒DNA,现在求有多少种长度位K的DNA,里面不含病毒DNA。
思路:路径(路径很长)种类一般都是需要矩阵优化的,这里把病毒DNA建立AC自动机,然后得到0可以到达的点(不形成病毒DNA)的和。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int Mod=100000;
const int maxn=128;
int ch[maxn][5],id[maxn],cnt;
int q[maxn],head,tail,Next[maxn],tag[maxn];
char s[20]; ll ans;
struct mat
{
    ll mp[maxn][maxn];
    mat(){memset(mp,0,sizeof(mp));}
    mat friend operator *(mat a,mat b)
    {
        mat res;
        for(int k=0;k<=cnt;k++)
          for(int i=0;i<=cnt;i++)
             for(int j=0;j<=cnt;j++){
               res.mp[i][j]=(res.mp[i][j]+a.mp[i][k]*b.mp[k][j])%Mod;
        } return res;
    }
    mat friend operator ^(mat a,int x)
    {
        mat res; for(int i=0;i<=cnt;i++) res.mp[i][i]=1;
        while(x){
            if(x&1) res=res*a; 
            a=a*a; x>>=1;
        }   return res;
    }
};
mat array;
struct ACautom
{
    void insert()
    {
        int Now=0;
        for(int i=0;s[i];i++){
            int x=id[s[i]];
            if(!ch[Now][x]) ch[Now][x]=++cnt;
            Now=ch[Now][x];
        } tag[Now]=1;
    }
    void build()
    {
        for(int i=0;i<4;i++){
            if(ch[0][i]) q[++head]=ch[0][i];    
            if(!tag[ch[0][i]]) array.mp[0][ch[0][i]]++;
        }
        while(tail<head){
            int u=q[++tail];
            for(int i=0;i<4;i++){
                if(ch[u][i]){
                    q[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                    if(tag[Next[ch[u][i]]]) tag[ch[u][i]]=1;
                }
                else ch[u][i]=ch[Next[u]][i];
                if(!tag[ch[u][i]]) array.mp[u][ch[u][i]]++;
            }
        }
    }
    void qpow(int K)
    {
        array=array^K;
        for(int i=0;i<=cnt;i++) ans=(ans+array.mp[0][i])%Mod;
        printf("%lld\n",ans);
    }
}Trie;
int main()
{
    int N,K,i;
    id['A']=0; id['G']=1; id['C']=2; id['T']=3;
    scanf("%d%d",&N,&K);
    for(i=1;i<=N;i++) {
        scanf("%s",s);
        Trie.insert();
    }
    Trie.build();
    Trie.qpow(K);
    return 0;
}
View Code

 

HDU2243:考研路茫茫——单词情结 (AC自动机+矩阵)

题意:问长度位1到L的所有单词中,有多少个不含给出的几个单词。
思路:和上一题差不多,但是要解决前缀和问题。有两种解决方案,一是二分;二是利用矩阵加一维,可以得到X^0+X^1+X^2...X^N。
比如得到26的0到N次幂和,就有矩阵a[0][0]=26,a[0][1]=1,a[1][0]=0,a[1][1]=1; 矩阵^N后,第一行的和就是答案。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ull unsigned long long
const int maxn=40;
int ch[maxn][26],cnt;
int q[maxn],head,tail,Next[maxn],tag[maxn];
char s[20];
struct mat
{
    ull mp[maxn][maxn];
    mat(){memset(mp,0,sizeof(mp));}
    mat init(){ memset(mp,0,sizeof(mp));}
    mat friend operator *(mat a,mat b)
    {
        mat res;
        for(int k=0;k<=max(cnt,2);k++)
          for(int i=0;i<=max(cnt,2);i++)
            for(int j=0;j<=max(cnt,2);j++)
             res.mp[i][j]+=a.mp[i][k]*b.mp[k][j];
        return res;
    }
    mat friend operator ^(mat a,int x)
    {
        mat res; 
        for(int i=0;i<=cnt;i++) 
            res.mp[i][i]=1;
        while(x){
            if(x&1) res=res*a; 
            a=a*a; x>>=1;
        }   return res;
    }
};

mat array;

struct ACautom
{
    void update()
    {
        cnt=head=tail=0;
        memset(Next,0,sizeof(Next));
        memset(tag,0,sizeof(tag));
        memset(ch,0,sizeof(ch));
        array.init();
    }
    void insert()
    {
        int Now=0;
        for(int i=0;s[i];i++){
            int x=s[i]-'a';
            if(!ch[Now][x]) ch[Now][x]=++cnt;
            Now=ch[Now][x];
        } tag[Now]=1;
    }
    void build()
    {
        for(int i=0;i<26;i++){
            if(ch[0][i]) q[++head]=ch[0][i];    
            if(!tag[ch[0][i]]) array.mp[0][ch[0][i]]++;
        }
        while(tail<head){
            int u=q[++tail];
            for(int i=0;i<26;i++){
                if(ch[u][i]){
                    q[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                    if(tag[Next[ch[u][i]]]) tag[ch[u][i]]=1;
                }
                else ch[u][i]=ch[Next[u]][i];
                if(!tag[ch[u][i]]) array.mp[u][ch[u][i]]++;
            }
        }
        cnt++;
        for(int i=0;i<=cnt;i++) array.mp[i][cnt]=1;
    }
    void qpow(int K)
    {
        ull ans,res=0;
        mat base;
        base.mp[0][0]=26; base.mp[0][1]=1;
        base.mp[1][0]=0;  base.mp[1][1]=1;
        base=base^K;
        ans=base.mp[0][0]+base.mp[0][1];
        array=array^K;
        for(int i=0;i<=cnt;i++) res=res+array.mp[0][i];
        cout<<ans-res<<endl;
    }
}Trie;
int main()
{
    int N,K;
    while(~scanf("%d%d",&N,&K)){
        Trie.update();
        for(int i=1;i<=N;i++) {
            scanf("%s",s);
            Trie.insert();
        }
        Trie.build();
        Trie.qpow(K);
    }
    return 0;
}
View Code

 

HDU2825:Wireless Password (AC自动机+状压DP)

题意:对于M个单词,求长度为N的字符串,至少包含了K个单词的数量。
思路:AC自动机+状压DP。注意有可能有相同的字符串,所以End处经常会用到"|="。
dp[L][X][K]表示长度为L时,在AC自动机上X节点,已经包含字符串情况为K的种类数。
复杂度25*100*1<<10
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=110;
const int Mod=20090717;
int dp[26][maxn][1<<10];
int Next[maxn],ch[maxn][26],End[maxn],N,M,K,cnt;
int que[maxn],head,tail; char s[maxn];
struct ACauto
{
    void update()
    {
        cnt=head=tail=0;
        memset(ch,0,sizeof(ch));
        memset(Next,0,sizeof(Next));
        memset(End,0,sizeof(End));
        memset(dp,0,sizeof(dp));
    }
    void insert(int tag)
    {
        int Now=0;
        tag=1<<tag; 
        for(int i=0;s[i];i++){
            if(!ch[Now][s[i]-'a']) ch[Now][s[i]-'a']=++cnt;
            Now=ch[Now][s[i]-'a'];
        } End[Now]|=tag;//不是等于,一位可能两个相同的单词
    }
    void build()
    {
        for(int i=0;i<26;i++){
           if(ch[0][i])
             que[++head]=ch[0][i];
        }
        while(tail<head){
            int u=que[++tail];
            for(int i=0;i<26;i++){
                if(ch[u][i]){
                    que[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                    End[ch[u][i]]|=End[Next[ch[u][i]]];
                }
                else ch[u][i]=ch[Next[u]][i];
            }
        }
    }
    bool count(int x)
    {
        int res=0;
        for(int i=0;i<M;i++){
            if((x>>i)&1) res++;
        }
        if(res>=K) return true;
        return false;
    }
    void solve()
    {
        dp[0][0][0]=1;
        for(int i=0;i<N;i++)
          for(int j=0;j<=cnt;j++)
             for(int p=0;p<(1<<M);p++){
              if(dp[i][j][p]){
                 for(int x=0;x<26;x++){
                   int newj=ch[j][x];
                   int newp=p|End[ch[j][x]];
                   dp[i+1][newj][newp]+=dp[i][j][p];
                   dp[i+1][newj][newp]%=Mod;
                }
              }
            }
        int ans=0;
        for(int i=0;i<=cnt;i++)
           for(int j=0;j<(1<<M);j++){
            if(count(j)) ans=(ans+dp[N][i][j])%Mod;
        }
        cout<<ans<<endl;
    }
}Trie;
int main()
{
    while(~scanf("%d%d%d",&N,&M,&K)){
        if(N==0&&M==0&&K==0) return 0;
        Trie.update();
        for(int i=0;i<M;i++){
            scanf("%s",s);
             Trie.insert(i);
        }
        Trie.build();
        Trie.solve();
    }
    return 0;
}
View Code

 

HihoCder1640 : 命名的烦恼(AC自动机+状压DP)

题意:给定N个字符串,问最短的字符串长度,使之包含这N个字符串。
思路:用dis[u][k]表示走到u点,已经包含的字符串情况k。然后在单调优先队列在AC自动机上bfs找最短路。(由于u太大,当然直接bfs是超时了的,需要DP做)
//代码没有AC 
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=50000;
const int inf=0x7fffffff;
int dp[1600][1<<15];
struct in
{
    int dis,pos,opt;
    in(int x,int y,int z):dis(x),pos(y),opt(z){}
    friend bool operator < (in a,in b){
        return a.dis>b.dis;
    }
};
priority_queue<in>q;
int Next[maxn],ch[maxn][26],End[maxn],N,cnt;
int que[maxn],head,tail; char s[maxn];
struct ACauto
{
    void insert(int tag)
    {
        int Now=0;
        for(int i=0;s[i];i++){
            if(!ch[Now][s[i]-'a']) ch[Now][s[i]-'a']=++cnt;
            Now=ch[Now][s[i]-'a'];
        } End[Now]=1<<tag;
    }
    void build()
    {
        for(int i=0;i<26;i++){
           if(ch[0][i])
             que[++head]=ch[0][i];
        }
        while(tail<head){
            int u=que[++tail];
            for(int i=0;i<26;i++){
                if(ch[u][i]){
                    que[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                    End[ch[u][i]]|=End[Next[ch[u][i]]];
                }
                else ch[u][i]=ch[Next[u]][i];
            }
        }
    }
    void solve()
    {
        dp[0][0]=0; q.push(in(0,0,0));
        while(!q.empty()){
            in tmp=q.top(); q.pop();
            int u=tmp.pos, k=tmp.opt;
            for(int i=0;i<26;i++){
                int v=ch[u][i],kk=k|End[v];
                if(v==0) continue;
                if(kk==(1<<N)-1) {
                    printf("%d\n",dp[u][k]+1);
                    return ;
                }
                if(dp[v][kk]>dp[u][k]+1) {
                    dp[v][kk]=dp[u][k]+1;
                    q.push(in(dp[v][kk],v,kk));
                }
            }
        }
    }
}Trie;
int main()
{
    scanf("%d",&N);
    for(int i=0;i<N;i++){
        scanf("%s",s);
        Trie.insert(i);
    }
    Trie.build();
    for(int i=0;i<=cnt;i++)
      for(int j=0;j<(1<<N);j++)
        dp[i][j]=inf;
    Trie.solve();
    return 0;
}
View Code

 

HDU2457:DNA repair(AC自动机+DP)

题意:给定一些病毒DNA,和一串人的NDA,问至少改变多少个核苷酸,使之不含病毒DNA。
思路:AC自动机把病毒NDA表示出来,在DP的时候不经过病毒DNA。 dp[len][X]表示前len个字符,最后一个字符在AC自动机上X点的最优解,方程易得。
#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1010;
const int Mod=20090717;
const int inf=1000000;
int dp[maxn][maxn];
int Next[maxn],ch[maxn][4],End[maxn],N,cnt;
int que[maxn],head,tail; char s[maxn];
int id[128],T;
struct ACauto
{
    void update()
    {
        cnt=head=tail=0;
        memset(ch,0,sizeof(ch));
        memset(Next,0,sizeof(Next));
        memset(End,0,sizeof(End));
    }
    void insert()
    {
        int Now=0;
        for(int i=0;s[i];i++){
            int x=id[s[i]];
            if(!ch[Now][x]) ch[Now][x]=++cnt;
            Now=ch[Now][x];
        } End[Now]=1;
    }
    void build()
    {
        for(int i=0;i<4;i++){
           if(ch[0][i])
             que[++head]=ch[0][i];
        }
        while(tail<head){
            int u=que[++tail];
            for(int i=0;i<4;i++){
                if(ch[u][i]){
                    que[++head]=ch[u][i];
                    Next[ch[u][i]]=ch[Next[u]][i];
                    End[ch[u][i]]|=End[Next[ch[u][i]]];
                }
                else ch[u][i]=ch[Next[u]][i];
            }
        }
    }
    void solve()
    {    
        scanf("%s",s);
        int Len=strlen(s);
        int ans=Len;
        for(int i=1;i<=Len;i++) 
           for(int j=0;j<=cnt;j++) dp[i][j]=inf;
          
        dp[0][0]=0;
    
        for(int i=0;s[i];i++){
            int x=id[s[i]];
        //    cout<<i<<" "<<s[i]<<" "<<x<<endl;
            for(int j=0;j<=cnt;j++){
                if(dp[i][j]==inf) continue;
                for(int p=0;p<4;p++){
                    if(End[ch[j][p]]) continue;
                    if(p==x) dp[i+1][ch[j][p]]=min(dp[i+1][ch[j][p]],dp[i][j]);
                    else dp[i+1][ch[j][p]]=min(dp[i+1][ch[j][p]],dp[i][j]+1);
                }
            }
        }
        for(int i=0;i<=cnt;i++) ans=min(ans,dp[Len][i]);
        printf("Case %d: ",++T);
        if(ans==Len) printf("-1\n");
        else printf("%d\n",ans); 
        return ;
    }
}Trie;
int main()
{
    id['A']=0; id['G']=1; id['C']=2; id['T']=3;
    while(~scanf("%d",&N)&&N!=0){
        Trie.update();
        for(int i=0;i<N;i++){
            scanf("%s",s);
             Trie.insert();
        }
        Trie.build();
        Trie.solve();
    }
    return 0;
}
View Code

 

posted @ 2018-02-24 14:13  nimphy  阅读(305)  评论(0编辑  收藏  举报