zoukankan      html  css  js  c++  java
  • [NOI2014] 动物园

    传送门 - > (bzoj3670) 动物园

    题目描述

    近日,园长发现动物园中好吃懒做的动物越来越多了。例如企鹅,只会卖萌向游客要吃的。为了整治动物园的不良风气,让动物们凭自己的真才实学向游客要吃的,园长决定开设算法班,让动物们学习算法。

    某天,园长给动物们讲解KMP算法。

    园长:“对于一个字符串 S ,它的长度为 L 。我们可以在 O(L) 的时间内,求出一个名为next的数组。有谁预习了next数组的含义吗?”

    熊猫:“对于字符串 S 的前 i 个字符构成的子串,既是它的后缀又是它的前缀的字符串中(它本身除外),最长的长度记作 next[i] 。”

    园长:“非常好!那你能举个例子吗?”

    熊猫:“例 S 为abcababc,则 next[5]=2 。因为 SS 的前 55 个字符为abcabab既是它的后缀又是它的前缀,并且找不到一个更长的字符串满足这个性质。同理,还可得出 next[1]=next[2]=next[3]=0 , next[4] = next[6] = 1 , next[7]=2 , next[8] = 3 。”

    园长表扬了认真预习的熊猫同学。随后,他详细讲解了如何在 O(L) 的时间内求出next数组。

    下课前,园长提出了一个问题:“KMP算法只能求出next数组。我现在希望求出一个更强大num数组一一对于字符串 S 的前 i 个字符构成的子串,既是它的后缀同时又是它的前缀,并且该后缀与该前缀不重叠,将这种字符串的数量记作 num[i] 。例如 S 为aaaaa,则 num[4]=2 。这是因为 S 的前 4 个字符为aaaa,其中aaa都满足性质‘既是后缀又是前缀’,同时保证这个后缀与这个前缀不重叠。而aaa虽然满足性质‘既是后缀又是前缀’,但遗憾的是这个后缀与这个前缀重叠了,所以不能计算在内。同理, num[1] = 0,num[2] = num[3] = 1,num[5] = 2。”

    最后,园长给出了奖励条件,第一个做对的同学奖励巧克力一盒。听了这句话,睡了一节课的企鹅立刻就醒过来了!但企鹅并不会做这道题,于是向参观动物园的你寻求帮助。你能否帮助企鹅写一个程序求出 num 数组呢?

    特别地,为了避免大量的输出,你不需要输出 num[i] 分别是多少,你只需要输出所有( num[i]+1 )的乘积,对 1,000,000,007 取模的结果即可。

    输入输出格式

    输入格式:

    第 1 行仅包含一个正整数 n ,表示测试数据的组数。
    随后 n 行,每行描述一组测试数据。每组测试数据仅含有一个字符串 S , S 的定义详见题目描述。数据保证 S中仅含小写字母。输入文件中不会包含多余的空行,行末不会存在多余的空格。

    输出格式:

    包含 n 行,每行描述一组测试数据的答案,答案的顺序应与输入数据的顺序保持一致。对于每组测试数据,仅需要输出一个整数,表示这组测试数据的答案对 1,000,000,007 取模的结果。输出文件中不应包含多余的空行。

    输入输出样例

    输入样例#1:

    3
    aaaaa
    ab
    abcababc

    输出样例#1:

    36
    1
    32

    说明

    测试点编号

    约定

    1. N ≤ 5, L ≤ 50
    2. N ≤ 5, L ≤ 200
    3. N ≤ 5, L ≤ 200
    4. N ≤ 5, L ≤ 10,000
    5. N ≤ 5, L ≤ 10,000
    6. N ≤ 5, L ≤ 100,000
    7. N ≤ 5, L ≤ 200,000
    8. N ≤ 5, L ≤ 500,000
    9. N ≤ 5, L ≤ 1,000,000
    10. N ≤ 5, L ≤ 1,000,000

    题解

    这道题要求出前缀与后缀不重合的公共前后缀的数量,也就是长度小于等于原字符串长度一半的公共前后缀的数量,我们依然可以通过kmp的预处理,求出最长公共前后缀,然后暴力向前跳,直到跳到0,然后统计长度小于一半的数量,每次ans累乘,但是这样最坏是(O(n^2))的, 无法接受,洛谷上50分

    50分Code

    #include<cstdio>
    #include<string>
    #include<iostream>
    #include<cstring>
    #include<cmath>
    #define Min(a,b) (a)<(b)?(a):(b)
    #define Max(a,b) (a)>(b)?(a):(b)
    #define in(i) (i=read())
    using namespace std;
    typedef long long lol;
    int read() {
        int ans=0,f=1; char i=getchar();
        while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
        while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
        return ans*f;
    }
    const int mod=1e9+7;
    lol ans=1,n,sum;
    int nex[1000010];
    char s[1000010];
    void get() {
        int p=0;
        for(int i=1;i<n;i++) {
            while(p && s[p]!=s[i]) p=nex[p];
            if(s[p]==s[i]) nex[i+1]=(++p);
            else nex[i+1]=0;
        }
    }
    void match() {
        for(int i=n;i>=1;i--) {
            int p=i,sum=0;
            while(p) {
                if(p<=(i>>1)) sum++;
                p=nex[p];
            }
            if(nex[i]) ans=(ans*(sum+1)%mod)%mod;
        }
    }
    int main()
    {
        int T; in(T);
        while(T--) {
            scanf("%s",s);
            n=strlen(s);ans=1;
            get(); match();
            printf("%lld
    ",ans);
        }
        return 0;
    }
    

    那么我们想怎么优化呢,可不可以把((n^2))变成(O(nlogn))呢?显然是可以的,我们发现next[]数组是可以不断向前跳满足单调性的,所以可以使用倍增优化,(next[i][j])表示i的前(2^j)个公共前后缀的位置,同时(2 ^j)也表示有多少个合法数量,方便我们统计,那么代码应该很容易打出来了

    80分Code

    #include<cstdio>
    #include<string>
    #include<iostream>
    #include<cstring>
    #include<cmath>
    #define in(i) (i=read())
    using namespace std;
    typedef long long lol;
    int read() {
        int ans=0,f=1; char i=getchar();
        while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
        while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
        return ans*f;
    }
    const int mod=1e9+7;
    lol ans=1,n,sum;
    int nex[1000010][20];
    char s[1000010];
    void get() {
        int p=0;
        for(int i=1;i<n;i++) {
            while(p && s[p]!=s[i]) p=nex[p][0];
            if(s[p]==s[i]) nex[i+1][0]=(++p);
            else nex[i+1][0]=0;
        }
    }
    void init() {
        for(int j=1;j<=19;j++)
            for(int i=n;i>=1;i--)
                nex[i][j]=nex[nex[i][j-1]][j-1];
    }
    void match() {
        for(int i=n;i>=1;i--) {
            int p=i,sum=0;
            for(int j=19;j>=0;j--) 
                if(nex[p][j]>(i>>1)) p=nex[p][j];
            for(int k=19;k>=0;k--) {
                if(nex[p][k]) p=nex[p][k],sum|=1<<k;
            }
            ans=(ans*(sum+1)%mod)%mod;
        }
    }
    int main()
    {
        int T; in(T);
        while(T--) {
            scanf("%s",s);
            n=strlen(s);ans=1;
            get(); init(); match();
            printf("%lld
    ",ans);
        }
        return 0;
    }
    

    这样就到了80分,然后听了大佬的玄学优化,把数组小的那一维放前面,方便取地址

    80分/bzojAC Code

    (bzoj - 5212ms)

    #include<bits/stdc++.h>
    #define in(i) (i=read())
    using namespace std;
    typedef long long lol;
    int read() {
        int ans=0,f=1; char i=getchar();
        while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
        while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
        return ans*f;
    }
    const int mod=1e9+7;
    lol ans=1;
    int n,sum;
    int nex[20][1000010];
    char s[1000010];
    void get() {
        int p=0;
        for(int i=1;i<n;++i) {
            while(p && s[p]!=s[i]) p=nex[0][p];
            if(s[p]==s[i]) nex[0][i+1]=(++p);
            else nex[0][i+1]=0;
        }
    }
    void init() {
        for(int j=1;j<=18;++j)
            for(int i=n;i>=1;--i)
                nex[j][i]=nex[j-1][nex[j-1][i]];
    }
    void match() {
        for(int i=n;i>=1;--i) {
            int p=i,sum=0;
            for(int j=18;j>=0;--j)
                if(nex[j][p]>(i>>1)) p=nex[j][p];
            for(int k=18;k>=0;--k) 
                if(nex[k][p]) p=nex[k][p],sum|=1<<k;
            ans=(ans*(sum+1)%mod)%mod;
        }
    }
    int main()
    {
        int T; in(T);
        while(T--) {
            scanf("%s",s);
            n=strlen(s);ans=1;
            get(); init(); match();
            printf("%lld
    ",ans);
        }
        return 0;
    }
    

    玄学玄学
    其实还是可以优化,我们可以先预处理一个cnt[i]数组,代表到1~i有多少个公共前缀,很明显cnt[p]=cnt[next[p]]+1
    先想最暴力的方法,从当前位置开始一直向前跳,但这样最坏复杂度是(O(n^2)),和暴力一样了
    那么我们模拟kmp的匹配方法,接着上次位置往前跳,跳到第一个小于等于i的一半的地方,直接用cnt[p]去统计就可以了,这样近似复杂度(O(n))
    跑的贼快

    AC Code

    (bzoj - 972ms)

    #include<bits/stdc++.h>
    #define in(i) (i=read())
    using namespace std;
    typedef long long lol;
    int read() {
        int ans=0,f=1; char i=getchar();
        while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
        while(i>='0' && i<='9') {ans=(ans<<1)+(ans<<3)+i-'0'; i=getchar();}
        return ans*f;
    }
    const int mod=1e9+7;
    lol ans=1;
    int n,sum;
    int nex[1000010],cnt[1000010];
    char s[1000010];
    void get() {
        int p=0;
        for(int i=1;i<n;++i) {
            while(p && s[p]!=s[i]) p=nex[p];
            if(s[p]==s[i]) nex[i+1]=(++p);
            else nex[i+1]=0;
        }
    }
    void match() {
        cnt[1]=1;
        for(int i=2;i<=n;++i) cnt[i]=cnt[nex[i]]+1;
        for(int i=0,p=0;i<n;++i) {
            while(p && s[p]!=s[i]) p=nex[p];
            p+=(s[p]==s[i]);
            while(p>(i+1>>1)) p=nex[p];
            ans=(ans*(cnt[p]+1)%mod)%mod;
        }
    }
    int main()
    {
        int T; in(T);
        while(T--) {
            scanf("%s",s);
            n=strlen(s);ans=1;
            get(); match();
            printf("%lld
    ",ans);
        }
        return 0;
    }
    

    博主蒟蒻,随意转载.但必须附上原文链接

    http://www.cnblogs.com/real-l/

  • 相关阅读:
    《软件方法》读书笔记2
    《代码阅读方法与实践》读书笔记3
    课堂讨论记录
    《代码阅读方法与实践》读书笔记2
    [洛谷] P1948 [USACO08JAN]Telephone Lines S(二分+SPFA)
    2020 CCPC秦皇岛 正式赛题解
    [洛谷] P3146 [USACO16OPEN]248 G (区间DP)
    [进阶指南] 最大子序和
    [训练] 图的K步移动最大收获
    [计蒜客] 受力平衡(组合数学 + 乘法逆元)
  • 原文地址:https://www.cnblogs.com/real-l/p/9486897.html
Copyright © 2011-2022 走看看