Trie、KMP、AC自动机小结

         最近做了不字符串的题,做下小结吧~

         首先是Trie(也叫前缀树),Trie的结构并不难理解,Trie是个树形结构,它的每条边对应一个字符,每个节点对应一个字符串的前缀(根节点对应空串),将给定的字符串建立起一棵Trie以后,查找一个串的复杂度就是O(L)的。在对应的节点处可以做各种标记,根据不同的情况有不同的用法,代码也很好写,不过Trie的空间要求比较大……下面贴几道题。。。。


hdu 1671 Phone List


给出一堆串,问是否有的串是另一个串的前缀。。比较简单的题吧,在每个单词结尾的节点打个标记,然后插入的时候判断是否到达某一个节点的末尾,当插入的单词到结尾时判断是否还能走就ok了


代码:


#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<vector>
#define inf 0x3f3f3f3f
#define Inf 0x3FFFFFFFFFFFFFFFLL
#define eps 1e-9
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
const int maxn=100000+10;
int ch[maxn][10],val[maxn],size;
char str[20];
void Init()
{
    memset(ch[0],0,sizeof(ch[0]));
    val[0]=0;size=0;
}
bool Insert(const char *s)
{
    int u=0,n=strlen(s);
    for(int i=0;i<n;++i)
    {
        int c=str[i]-'0';
        if(!ch[u][c])
        {
            ch[u][c]=++size;
            memset(ch[size],0,sizeof(ch[size]));
            val[size]=0;
        }
        else if(i==n-1) return false;
        u=ch[u][c];
        if(val[u]) return false;
    }
    val[u]=1;
    return true;
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n;
        bool flag=true;
        Init();
        scanf("%d",&n);
        for(int i=0;i<n;++i)
        {
            scanf("%s",str);
            flag=flag&&Insert(str);
        }
        if(flag) puts("YES");
        else puts("NO");
    }
    return 0;
}



UVALive 3942   Remember the Word(Trie+DP)


      给出n个字典串和一个长串,把这个字符串分解成若干个单词连接,有多少种方法。

      不错的题,先建立字典树,然后DP搞之。

     题解:

          http://blog.csdn.net/qian99/article/details/18730455


UVA 11732   strcmp() Anyone

        给出一个strcmp()函数,再给出n个字符串,问这n个字符串使用给出的函数两两比较,总共需要比较的次数。也是不错的题,把字符串边插入边计算即可,做了这题会让你对Trie的结构理解的更深一点。比较坑爹的是时限比较严,用了左儿子右兄弟表示法才过掉……

       题解:

          http://blog.csdn.net/qian99/article/details/18735485


UVA 11488   Hyper Prefix Sets(Trie)

       给出n个串,问这n个串的公共前缀乘字符个数的最大值。这题跟上一题差不多,也是边插入边算就行了。

      题解:

         http://blog.csdn.net/qian99/article/details/18736843



         接下来就是KMP了,KMP网上资料非常多,也就不多说了(省得丢人啊)……


hdu 2087 剪花布条

          给出两个串A和B,问B在A中的匹配次数(不重叠)。

          比较简单的KMP应用吧,构造完next数组后去匹配,和普通匹配不同的是,匹配成功以后不能退出,而是答案+1,并且将状态置0,接着匹配。


代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<vector>
#define inf 0x3f3f3f3f
#define Inf 0x3FFFFFFFFFFFFFFFLL
#define eps 1e-9
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
const int maxn=1000+10;
char s1[maxn],s2[maxn];
int next[maxn],ans;
void Kmp()
{
    int n=strlen(s1);
    int m=strlen(s2);
    if(m>n) return ;
    int j=0;
    for(int i=0;i<n;++i)
    {
        while(j&&s1[i]!=s2[j]) j=next[j];
        if(s1[i]==s2[j]) j++;
        if(j==m) {ans++;j=0;}
    }
}
void getnext()
{
    int n=strlen(s2);
    next[0]=next[1]=0;
    for(int i=1;i<n;++i)
    {
        int j=next[i];
        while(j&&s2[i]!=s2[j]) j=next[j];
        next[i+1]=(s2[i]==s2[j])?j+1:0;
    }
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    while(~scanf("%s",s1))
    {
        if(s1[0]=='#') break;
        scanf("%s",s2);
        ans=0;
        getnext();
        Kmp();
        printf("%d\n",ans);
    }
    return 0;
}


hdu 3336 Count the string

           给出一个字符串,问它的所有前缀和它匹配的次数。其实KMP求出的next函数是一个状态转移的函数,当发生转移时,如果转移到0,此时是空串,如果不是,那么说明当前状态和前面是有匹配的。

         题解:

             http://blog.csdn.net/qian99/article/details/18768235


hdu 3746 Cyclic Nacklace


             给出一个串,问最少在串末尾添加几个字符,才能让串中出现循环。KMP的性质,在题解里应该说的比较明白~

           题解:

hdu 1358 Period
         给出一个串,求这个串每个前缀能否形成循环,并输出位置和循环节。明白上一题,这题就非常好做了~
          题解:


hdu 2594 Simpsons’ Hidden Talents
          给出两个串S1和S2,求S1的一个最长前缀,并且满足它是S2的后缀。蛮有意思的一道题,用S1匹配S2,一直匹配到结尾,返回成功匹配的长度。
         题解:


          KMP告一段落,下面是AC自动机~AC自动机可以看成是Trie和KMP的结合……不过这么说也不太准确,我的理解是KMP和AC自动机都是一种有限自动机,有限自动机的概念可以看一下算导,理解了这个,KMP和AC自动机都不难理解了。其实KMP和AC自动机都建立起了一个状态转移图,只不过KMP是一条链。AC自动机建立起来以后对于每个字符都会转移到下一个状态,而在一些状态下保存信息(比如在单词结尾做标记),就可以处理多个串和一个串匹配的问题。另外由于每次添加一个字符都会发生状态转移,所以总会跟着DP一起出现……
          下面的题基本都是从kuangbin巨巨那里淘的……


UVALive 4670   Dominating Patterns
           给出n个串,问文本串A中哪些串出现的次数最多。基本上市模板题,需要注意的是给出的串有可能重复,这里用map搞一下就好了。
      题解:


UVA 11468   Substring
           给出文章中可以出现的字符以及出现的概率,在给出n个模板串,求随机生成长度为L的串并且不包含任何模板串的概率。
           AC自动机+记忆化搜索……dp[u][L]表示当前在状态u,字符串还需要添加L个字符并且不包含任何模板串的概率……AC自动机建立的时候在所有模板串的末尾打标记,表示该状态不能到达。然后直接记忆化搜索即可。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<vector>
#define inf 0x3f3f3f3f
#define Inf 0x3FFFFFFFFFFFFFFFLL
#define eps 1e-9
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
const int maxn=2000+10;
double p[110],dp[maxn][110];
bool vis[maxn][110];
int ch[maxn][63],val[maxn],next[maxn],size,n;
int idx(char c)
{
    if(c>='0'&&c<='9') return c-'0';
    if(c>='a'&&c<='z') return c-'a'+10;
    return c-'A'+10+26;
}
void Init()
{
    memset(vis,0,sizeof(vis));
    memset(ch[0],0,sizeof(ch[0]));
    memset(next,0,sizeof(next));
    memset(val,0,sizeof(val));
    memset(p,0,sizeof(p));
    size=0;
}
void insert(const char *s)
{
    int u=0,len=strlen(s);
    for(int i=0;i<len;++i)
    {
        int c=idx(s[i]);
        if(!ch[u][c])
        {
            ch[u][c]=++size;
            memset(ch[size],0,sizeof(ch[size]));
            val[size]=0;
        }
        u=ch[u][c];
    }
    val[u]=1;
}
void build()
{
    queue<int>q;
    for(int i=0;i<62;++i)
    {
        if(ch[0][i]) q.push(ch[0][i]);
    }
    while(!q.empty())
    {
        int u=q.front();q.pop();
        for(int i=0;i<62;++i)
        {
            int v=ch[u][i];
            if(!v) {ch[u][i]=ch[next[u]][i];continue;}
            q.push(v);
            int j=next[u];
            while(j&&!ch[j][i]) j=next[j];
            next[v]=ch[j][i];
            val[v]|=val[next[v]];
        }
    }
}
double f(int u,int L)
{
    if(L==0) return 1.0;
    if(vis[u][L]) return dp[u][L];
    vis[u][L]=true;
    dp[u][L]=0;
    for(int i=0;i<62;++i)
    {
        if(!val[ch[u][i]])
            dp[u][L]+=p[i]*f(ch[u][i],L-1);
    }
    return dp[u][L];
}
char str[110];
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int t,tcase=0;
    scanf("%d",&t);
    while(t--)
    {
        tcase++;
        Init();
        int K;
        scanf("%d",&K);
        for(int i=0;i<K;++i)
        {
            scanf("%s",str);
            insert(str);
        }
        scanf("%d",&n);
        char c[3];
        for(int i=0;i<n;++i)
        {
            scanf("%s",c);
            scanf("%lf",&p[idx(c[0])]);
        }
        build();
        int L;
        scanf("%d",&L);
        double ans=f(0,L);
        printf("Case #%d: %lf\n",tcase,ans);
    }
    return 0;
}



poj 1625 Censored!(AC自动机+DP+高精)

        给出n个模式串,问生成长度为M并且不包含任何模式串的方案数。做法和上一题差不多,只不过这题要写个高精。

        题解:


hdu 2825 Wireless Password(AC自动机+状压DP)
          给出m个串,现在要生成一个长度为n的串,并且这个串至少包含k个给出的串,问生成串的方案数。思路都形似,把k用二进制表示,然后一个三维Dp搞之。

        题解:

poj 2778 DNA Sequence(AC自动机+矩阵快速幂)
           这题和poj 1625意思一样,但是做法不同,原因是要生成的串的长度太长……这里的做法就是把所有的状态可以发生转移的情况生成一个矩阵,然后用矩阵快速幂做。。。

代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<stack>
#include<cmath>
#include<vector>
#define inf 0x3f3f3f3f
#define Inf 0x3FFFFFFFFFFFFFFFLL
#define eps 1e-9
#define pi acos(-1.0)
using namespace std;
typedef long long ll;
const int maxn=200+10;
const int mod=100000;
int ch[maxn][4],next[maxn],flag[maxn],size;
int indx[maxn];
void Init()
{
    memset(ch[0],0,sizeof(ch[0]));
    memset(next,0,sizeof(next));
    indx['A']=0;indx['C']=1;indx['T']=2;indx['G']=3;
    size=0;
}
void Insert(const char * s)
{
    int u=0,n=strlen(s);
    for(int i=0;i<n;++i)
    {
        int c=indx[s[i]];
        if(!ch[u][c])
        {
            ch[u][c]=++size;
            memset(ch[size],0,sizeof(ch[size]));
            flag[size]=0;
        }
        u=ch[u][c];
    }
    flag[u]=1;
}
void build()
{
    queue<int>q;
    for(int i=0;i<4;++i)
        if(ch[0][i]) q.push(ch[0][i]);
    int r,u,v;
    while(!q.empty())
    {
        r=q.front();q.pop();
        for(int c=0;c<4;++c)
        {
            u=ch[r][c];
            if(!u) {ch[r][c]=ch[next[r]][c];continue;}
            q.push(u);
            v=next[r];
            while(v&&!ch[v][c]) v=next[v];
            next[u]=ch[v][c];
            flag[u]|=flag[next[u]];
        }
    }
}
char str[55];
ll matrix[maxn][maxn],res[maxn][maxn],tmp[maxn][maxn];
void mul(ll a[maxn][maxn],ll b[maxn][maxn])
{
    for(int i=0;i<=size;++i)
        for(int j=0;j<=size;++j)
        {
            tmp[i][j]=0;
            for(int k=0;k<=size;++k)
            {
                tmp[i][j]+=a[i][k]*b[k][j];
                tmp[i][j]%=mod;
            }
        }
    for(int i=0;i<=size;++i)
        for(int j=0;j<=size;++j)
            b[i][j]=tmp[i][j];
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    int m,n;
    Init();
    scanf("%d%d",&m,&n);
    for(int i=0;i<m;++i)
    {
        scanf("%s",str);
        Insert(str);
    }
    build();
    memset(matrix,0,sizeof(matrix));
    memset(res,0,sizeof(res));
    for(int i=0;i<=size;++i)
    {
        if(flag[i]) continue;
        for(int c=0;c<4;++c)
            if(!flag[ch[i][c]])
                matrix[i][ch[i][c]]++;
    }
    for(int i=0;i<=size;++i) res[i][i]=1;
    while(n)
    {
        if(n&1) mul(matrix,res);
        mul(matrix,matrix);
        n>>=1;
    }
    int ans=0;
    for(int i=0;i<=size;++i)
        ans=(ans+res[0][i])%mod;
    printf("%d\n",ans);
    return 0;
}


HDU 2296   Ring(AC自动机+DP)
 
       给出m个串,每个串都有一个价值,现在要构造一个长度小于等于n的串,使得其包含的价值最大。这题思路挺好想的,但是要输出路径,最开始记录的字符,怎么写都不太对,后来弃疗了,直接每个状态记录了个串。

       题解:


hdu 2457 DNA repair(AC自动机+DP)
        给出n个病毒串,然后给出一个长串 ,可以任意修改这个串,要求这个串不包含病毒串,问最小修改次数。构建完AC自动机以后也是简单dp~

       题解:

ZOJ 3228   Searching the String
       给出n个串,每次询问这些串在A中的匹配数,有些串可以覆盖,有些串不可以覆盖。这里记录一下串的长度就行了,不是很难。
       题解:

hdu 3341 Lost's revenge(AC自动机+DP)
       给出n个串,重排串A,使得n个串在A中匹配数最多。状态转移不难想,只是状态不是很好表示,正常表示一定会爆内存,其实表示方法挺多的,随便搞搞就行了~

      题解:

hdu 3247 Resource Archiver(AC自动机+状压DP)
          给出m个病毒串,现在要把n个串合并,并且使得合并后的串中不包含病毒串。做得非常心酸,调了半天bug发现有个地方没赋初值,唉,又犯2了……

        题解:

ZOJ 3494   BCD Code(AC自动机+数位DP)
          给出一些禁止串,问从A~B的数字用BCD码表示,并且不包含禁止串的数有多少。AC自动机真是和什么都能结合啊,好神奇~

        题解:


          差不多就到这里了~最近做字符串有些恶心了,该做点儿别的调节调节心情了~以后再做或许还会发?

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值