[BZOJ3670][Noi2014]动物园(KMP)

=== ===

这里放传送门

=== ===

题解

写这题的时候我还不会在本机开栈,为了写对拍硬生生把dfs改成了非递归。。。然后这代码就看起来超长。。。。

这道题的意思是对于一个字符串定义一个num[i]为前缀p[1..i-1]中满足“既是前缀p[i]的前缀(啊废话那肯定是它的前缀)又是它的后缀并且前缀和后缀不重叠”的数目,然后要对整个串求一个 i=1nnum[i] 。显然这玩意儿跟KMP里面的失配函数有很大关系。。并且很容易想到一种暴力求num数组的做法就是对于每个位置顺着失配函数next一路追溯到0,统计next值小于等于i/2的就可以了。目测随机数据这种做法差不多能过,但是如果像是整个串都是同一种字符的那种的话它就稳稳 n2 了。。。

观察这个暴力,它很暴力的原因就是每次向前蹦的时候很多位置被重复跳了很多次,并且一旦跳到了以前跳过的位置,接下来跳的情况就跟以前相同了。所以如果能对每个走过的节点维护信息,然后保证每个点只经过一遍,就能把这个时间复杂度压到科学的 O(n)

观察失配函数的建立过程,我们可以发现一个点只会连到唯一的一个点,但是可能被很多点连到。这是一个标准的树结构,那么如果把树上每个点的权值赋值为它的next值,我们要求的就是对每个点它到树根的路径上有多少个点的权值大于i/2。那么我们可以发现一条链上的点好像是可以一块求的。如果维护一个指针指向对于当前点i,满足条件的最靠下的一个祖先,那显然上面的所有点都满足条件,那么答案就是deep值;并且随着i的下移,这个指针的位置是单调不上升的。于是在dfs中维护这个指针,每次让它尽量下移,然后统计deep。记录一下这条路径从哪条边走过来便于指针下放。注意指针不能移动到当前节点的下面否则就不是它的祖先了。所以要加一条特判,好像在失配函数有连续一大段为0的时候会起作用。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define Mod 1000000007
using namespace std;
int n,len,tot,p[1000010],a[2000010],next[2000010],T[1000010],cur[1000010],fa[1000010],e[1000010],flast[1000010],fdep[1000010];
char S[1000010];
bool vis[1000010];
void add(int x,int y){tot++;a[tot]=y;next[tot]=p[x];p[x]=tot;}
long long ans;
/*void dfs(int u,int last,int deep){
    int x=a[cur[last]],nowdep=deep;
    while (T[x]<=u/2&&u!=0){
        last=x;nowdep+=1;
        if (x==u) break;
        x=a[cur[last]];
    }
    if (u!=0) ans=ans*(nowdep+1)%Mod;
    for (int i=p[u];i!=0;i=next[i]){
        cur[u]=i;dfs(a[i],last,nowdep);
    }
}*/
void dfs(){
    int u=0,last=0,deep=-1;
    bool flag;
    while (true){
        if (vis[u]==false){
            int x=a[cur[last]];
            vis[u]=true;
            while (T[x]<=u/2&&u!=0){//如果这个祖先不符合要求并且还可以再下移
                last=x;deep+=1;//deep维护当前祖先的深度
                if (x==u) break;
                x=a[cur[last]];//顺着当前路径下移
            }
            if (u!=0) ans=ans*(deep+1)%Mod;
        }
        flag=false;
        for (int i=e[u];i!=0;i=next[i]){
            cur[u]=i;e[u]=next[i];
            fa[a[i]]=u;flast[a[i]]=last;
            fdep[a[i]]=deep;u=a[i];
            flag=true;break;
        }
        if (flag==false){
            if (u==0) break;
            last=flast[u];deep=fdep[u];
            u=fa[u];//得到上一层的信息并回溯
        }
    }
}
int main()
{
    scanf("%d",&n);
    for (int wer=1;wer<=n;wer++){
        memset(p,0,sizeof(p));
        T[0]=-1;tot=0;
        scanf("%s",S);
        len=strlen(S);
        for (int i=0;i<len;i++){
            int j=T[i],pos=i;
            while (j!=-1&&S[j]!=S[i])
            {pos=j;j=T[j];}
            T[i+1]=j+1;
            add(j+1,i+1);//建立KMP树
        }
        for (int i=0;i<=len;i++){
            cur[i]=p[i];e[i]=p[i];vis[i]=false;
        }
        ans=1;dfs();
        printf("%I64d\n",ans%Mod);
    }
    return 0;
}

偏偏在最后出现的补充说明

KMP树是一个超级有用的东西啊。。。好多时候一些在字符串上统计信息的题都可以考虑一下KMP树呢。
这道题的做法是典型的“优化暴力”的思路啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值