zoukankan      html  css  js  c++  java
  • 机房测试16:字符串专题(AC自动机+dp+kmp)

    T1:

    一只青蛙失去了荷叶的保护。
     它十分迷茫,于是决定改变自己的基因,让自己成为一个受庇护的不会老去的物种。众
    所周知,青蛙的基因是一段有遗传效应 DNA 片段,我们认为这个片段仅由“A”,“ T”,
    “C”,“ G”组成,为了方便,我们只需考虑DNA的一条链。
     这只青蛙十分有经验,它知道这条链长度为 n,并且知道有庇护效应的序列有 m 种,
    它希望使得这条链上的每一个位置都受到庇护。注意有庇护效应的序列可以相互重叠,一个
    位置被庇护当且仅当它被至少一个有庇护效应的序列包含。
     这只青蛙想要知道有多少种满足条件的链,对 109+9取模。

    对于100%的数据1<=n<=1000,1<=m<=10 , 有庇护效应的序列长度不超过10

    分析:

    涉及到多个字符串匹配的问题,首先考虑AC自动机。

    定义dp[i][j][k]为已凑出i的长度,在AC自动机的j号节点,从后往前数第k个位置还未被覆盖(且是最左的未被覆盖的)。

    利用AC自动机跳fail预处理出每一个节点包含的最长庇护长度,转移的时候就判断覆盖的长度是否大于等于k+1(包括了k点),是就说明可以将k覆盖,转移到dp[i+1][son][0](son是当前节点的儿子节点)

    否则就转移到dp[i+1][son][k+1]    k+1是因为多了这个节点没被覆盖。

    #include<bits/stdc++.h>
    using namespace std;
    #define N 105
    #define ri register int
    const int mod=1e9+9;
    int ndnum=0,ed[N],go[N][5],r[N],fail[N],dp[1005][N][12],n,m;
    char s[15];
    void add()
    {
        int len=strlen(s+1),now=0;
        for(ri i=1;i<=len;++i){
            int c=r[(int)s[i]];//printf("%d
    ",c);
            if(!go[now][c]) go[now][c]=++ndnum;
            now=go[now][c];
        }
        ed[now]=len;
    }
    void get_fail()
    {
        queue<int> q;
        for(ri i=0;i<=3;++i) if(go[0][i]) q.push(go[0][i]);
        while(!q.empty()){
            int now=q.front(); q.pop();//printf("now:%d
    ",now);
            for(ri i=0;i<=3;++i){
                if(!go[now][i]) go[now][i]=go[fail[now]][i];
                else{
                    fail[go[now][i]]=go[fail[now]][i];
                    q.push(go[now][i]);
                    ed[go[now][i]]=max(ed[go[now][i]],ed[fail[go[now][i]]]);
                    //通过求fail指针的同时 求出以这个点结尾的能覆盖的最长长度 
                }
            }
        }
    }
    void work()
    {
        dp[0][0][0]=1;
        for(ri i=0;i<n;++i)
         for(ri j=0;j<=ndnum;++j)//0是根节点 
          for(ri k=0;k<=9;++k){//9是因为要转移到k+1  
              if(!dp[i][j][k]) continue;
              for(ri p=0;p<=3;++p){
                  int son=go[j][p];//遍历当前节点的每一个儿子 
                  if(ed[son]>=k+1) dp[i+1][son][0]=(dp[i+1][son][0]+dp[i][j][k]) %mod;
                //如果可以覆盖 就转移到只有倒数0位没有被覆盖 否则加上这一个就多了一个没有被覆盖的 转移到k+1 
                  else dp[i+1][son][k+1]=(dp[i+1][son][k+1]+dp[i][j][k]) %mod;
            }
        }
        int ans=0;
        for(ri i=0;i<=ndnum;++i) ans=(ans+dp[n][i][0]) %mod;
        printf("%d
    ",ans);
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        r[(int)'A']=0; r[(int)'C']=1; r[(int)'G']=2; r[(int)'T']=3;
        for(ri i=1;i<=m;++i) scanf("%s",s+1),add();
        get_fail();
        work();
        return 0;
    }
    /*
    6 2 
    CAT 
    TACT 
    */
    View Code

    T2:

     在获得庇护的同时,这只青蛙(或者称为别的什么物种)还获得了一股强大的力量。它的
    存在已经超出了自然世界的其他一切生物,于是它想要突破界限,掌握这股力量。
     现在它得到了一本古老的书籍,并且将其翻译得到了一个长为 n 的由小写字母构成的
    字符串,现在,它想要找到一个最长的子串,使得这个子串既是字符串的前缀又是后缀,并
    且在字符串的中间出现过(即在非前后缀的位置出现过)。
     由于剧情需要,我们保证存在这样一个满足条件的子串。

    对于100%的数据,n<=10000000

    分析:

    考虑kmp中nex数组的定义:nex[i]=j表示1~j与i-j+1~i匹配,且j最大,也就是最长的前缀与后缀相同。

    但这道题还有一个限制:必须在中间出现过。如果直接取nex[n],并且中间有一个位置满足条件,那么一定会有nex[x]==nex[n]。

    如果不存在这样的x,说明长度应该缩小,怎么缩呢,跳nex!!

    原本长度为3,因为不满足中间出现过,所以通过跳n的nex来缩短长度,找到一个更短的,满足中间出现过的。

    #include<bits/stdc++.h>
    using namespace std;
    #define ri register int
    #define N 10000005
    int nex[N],n;
    char s[N];
    bool vis[N];
    void get_next()
    {
        nex[0]=nex[1]=0;
        for(ri i=2,j=0;i<=n;++i){
            while(j>0 && s[i]!=s[j+1]) j=nex[j];
            if(s[i]==s[j+1]) j++;
            nex[i]=j;
            if(i!=n) vis[j]=true;
        }
        for(ri i=1;i<=n;++i)
        printf("%d ",nex[i]);
    }
    int main()
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        get_next();
        int now=n;
        while(!vis[nex[now]] && now>0) now=nex[now];
        printf("%d
    ",nex[now]);
    }
    /*
    abababadababa
    */
    View Code

    T3:

    这只小动物找到了书中的力量,它几乎就要成功了,依据书中内容,它还缺一副眼镜。

    于是它找到了一个 01 串,想要从中找到制造眼镜的材料。它希望找到这个 01 串的最
    长的子序列串(即不要求连续),这个子序列满足01交间的性质(01010…或10101…)。
    但是在寻找之前,它想测试一下目前拥有的力量,于是它选择了一段连续的区间,将这
    个区间中的0变成1、1变成0,再在其中寻找最长01交间子序列串。
    当然,它希望合适地运用仅一次自己的力量(但一定要使用),使得这个子序列串变得尽
    可能长。

    分析

    通过画图,出多组数据分析可知,无论怎么翻转,每次对答案最多贡献2(翻转的区间对左边和右边分别+1,中间是不会变的),所以只需要求出初始01串长,然后再+2即可(注意和n取min)

    #include<bits/stdc++.h>
    using namespace std;
    #define N 500005
    #define ri register int
    char s[N];
    int a[N],tmp[N],n;
    int check(int l,int r)
    {
        memcpy(tmp,a,sizeof(a));
        for(ri i=l;i<=r;++i) tmp[i]^=1;
        int cnt1=0,cnt2=0,need=0;
        for(ri i=1;i<=n;++i) if(tmp[i]==need) cnt1++,need^=1;
        need=1;
        for(ri i=1;i<=n;++i) if(tmp[i]==need) cnt2++,need^=1;
        return max(cnt1,cnt2);
    }
    int main()
    {
        scanf("%s",s+1);
        n=strlen(s+1);
        int ans=0;
        for(ri i=1;i<=n;++i) a[i]=s[i]-'0';
        printf("%d
    ",min(check(0,0)+2,n));
    }
    /*
    10000011
    
    11000111100001010101
    
    10010110011000001111
    10101010101
    */
    View Code
  • 相关阅读:
    python相关遗漏知识点补充
    关于viewpager的滑动问题
    C++学习一二
    Neo4j 爬坑笔记for3.2.6
    ZTree简单粗暴快速使用
    阅读HashMap——jdk7时遇到的问题记录
    【安装】Hadoop2.8.0搭建过程整理版
    html、jsp页面标签的遍历
    tomcat配置多个数据源
    java线程
  • 原文地址:https://www.cnblogs.com/mowanying/p/11712558.html
Copyright © 2011-2022 走看看