【题解】CF 1129C. Morse Code

题意:给一长度为m的字符串,只包含0和1。字符组合可以表示字母,已知1个字符,2个字符,3个字符,4个字符(除0011,0101,1110,1111)均可表示一个字母。问该字符串的所有非空子串所能表示的字母序列的个数。\(1\leq m\leq 3000\)

名词解释

  • s的sum:字符串s所能表示的字母序列的个数。
  • s[i,j]:字符串s的第i位到第j位。下标从1开始。

思路:受到597C题的启发,对于询问多字符表示字符情况数的问题可以dp来做。dp[i]表示前i位构成的字符串的sum,则转移方程为dp[i]=dp[i-1]+dp[i-2]+dp[i-3]+dp[i-4]。这一点容易理解,可以第i位表示一个字母,方案数是dp[i-1],第i位+第i-1位表示一个字母,方案数是dp[i-2],以此类推。注意这里有四个4位字符是不能表示字母的,所以在+dp[i-4]之前先check一下i-3,i-2,i-1,i四个字符是否是那四种情况,如果是的话就不加。dp的时间复杂度是O(n)。

然而本题要求的是所有子串(本质不同)的sum之和。所有子串的数量为\(\frac{n(n+1)}{2}\)个。这一点可以这样考虑,s[1,n]构成的字符串后缀数量为n,s[1,n-1]后缀数量为n-1,以此类推,所有这些后缀加起来构成了所有子串。之后我们还要去掉重复字符串,但子串数量肯定是\(O(n^2)\)级别的,每个字符串都做一次dp,那么复杂度就炸了。

但恰好从枚举子串的思路入手,我们dp的时候是逐位dp,恰好依次枚举了字符串的所有前缀,也就是计算出了所有前缀的sum。事实上我们从最后一位往前dp也是一样的,即dp[i]=dp[i+1]+dp[i+2]+dp[i+3]+dp[i+4],也就是计算出字符串的所有后缀的sum。那么就仿照枚举所有子串的思路,对s[1,n],s[1,n-1]……s[1,1]这n个字符串这么做,就顺手求出了所有子串的sum,这样就\(O(n^2)\)了。

但怎么去重?事实上没法去重。

所以只能换个思路,我们直接枚举所有本质不同的字符串,这一点可以借助后缀自动机。hihocoder上面那个图记忆犹新:

https://hihocoder.com/problemset/problem/1441 罗列子串那个图,我觉得非常非常清晰和重要

所有节点代表的字符串加起来就是所有本质不同的子串,而且非常友好的是每个节点的字符串都是maxlen的后缀,正好可以用我们的方法去算。

但上面这个想法只是口胡,怎么可能去枚举所有节点?想到这里我卡住了。中间试图考虑每添加一个字符,就记录这个字符所对应的子串,但后来去偷偷看了qz爷的题解,找到了最关键的一句话:

在字符串的尾部新加入一个字符时,会新增一些本质不同的子串,这些子串都属于后缀自动机的最后一个节点。并且在字符串中的位置是:[1,i],[2,i]...[len[fa[last]],i]

扫描二维码关注公众号,回复: 7782371 查看本文章

所以只要加入第i个字符后,对s[1,i]从后往前跑一遍dp,之后这个串的sum就是dp[1]+dp[2]+...+dp[i-len[fa[last]]]。

复杂度:总共需要跑m次dp,每个dp复杂度是O(m),总复杂度\(O(m^2)\)

自己对后缀自动机的各种性质还是不够了解,不然应该不用看qz爷题解就能想出来正解的。另外多学了一个dp方法,597C我当时并没有做出来也是看题解才会的。

代码才53行,极其简洁,比qz爷的好多了(不是

AC代码:93ms,112KB

#include<bits/stdc++.h>
using namespace std;
const int M=3000+20,P=1e9+7;
char s[M];
int last,cnt,ch[M<<1][2],fa[M<<1],len[M<<1];
void ins(int c){
    int p=last,np=++cnt;
    last=np,len[np]=len[p]+1;
    for(;p&&!ch[p][c];p=fa[p])
        ch[p][c]=np;
    if(!p)
        fa[np]=1;
    else{
        int q=ch[p][c];
        if(len[p]+1==len[q])
            fa[np]=q;
        else{
            int nq=++cnt;
            len[nq]=len[p]+1;
            memcpy(ch[nq],ch[q],sizeof(ch[q]));
            fa[nq]=fa[q],fa[q]=fa[np]=nq;
            for(;ch[p][c]==q;p=fa[p])
                ch[p][c]=nq;
        }
    }
}
int main(){
    int lenn,c,ans=0;
    scanf("%d",&lenn);
    last=cnt=1;
    for(int i=1;i<=lenn;i++){
        scanf("%d",&c),s[i]=c,ins(c);
        int lmn=len[fa[last]]+1;
        int dp[M];
        dp[i+1]=1;
        for(int j=i;j>=1;--j){
            dp[j]=dp[j+1];
            if (j+2<=i+1)
                dp[j]=(dp[j]+dp[j+2])%P;
            if (j+3<=i+1)
                dp[j]=(dp[j]+dp[j+3])%P;
            if (j+4<=i+1
            &&(!(s[j]==0&&s[j+1]==0&&s[j+2]==1&&s[j+3]==1))
            &&(!(s[j]==0&&s[j+1]==1&&s[j+2]==0&&s[j+3]==1))
            &&(!(s[j]==1&&s[j+1]==1&&s[j+2]==1)))
                dp[j]=(dp[j]+dp[j+4])%P;
        }
        for (int j=1;j<=i-lmn+1;++j)
            ans=(ans+dp[j])%P;
        cout<<ans<<endl;
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/diorvh/p/11817249.html