机房测试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
原文地址:https://www.cnblogs.com/mowanying/p/11712558.html