zoukankan      html  css  js  c++  java
  • 【题解】CF 1129C. Morse Code

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

    所以只要加入第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;
    }
    
  • 相关阅读:
    数据库Mysql给用户赋予操作表的权限
    C# log4net日志分等级打日志
    C# 将字符串转为函数名
    C# winform无法拖动控件
    C# 程序获取管理员方法
    C# 生成程序目录避免生成多余的XML和pdb
    C# 快速获取一个月的天数或最后一天
    正则
    C# 根据服务名打开所在文件夹
    330 div+css Experience
  • 原文地址:https://www.cnblogs.com/diorvh/p/11817249.html
Copyright © 2011-2022 走看看