[BZOJ 1535] [Luogu 3426]SZA-Template (KMP+fail树+双向链表)

[BZOJ 1535] [Luogu 3426]SZA-Template (KMP+fail树+双向链表)

题面

Byteasar 想在墙上涂一段很长的字符,他为了做这件事从字符的前面一段中截取了一段作为模版. 然后将模版重复喷涂到相应的位置后就得到了他想要的字符序列.一个字符可以被喷涂很多次,但是一个位置不能喷涂不同的字符.做一个模版很费工夫,所以他想要模版的长度尽量小,求最小长度是多少.

拿样例来说ababbababbabababbabababbababbaba , 模版为前8个字符ababbaba, 喷涂的过程为: ababbababbabababbabababbababbaba

分析

分析模板串,我们会发现3条性质

模板串性质:

  1. 一个模板串A是要求的文本串B的公共前后缀
  2. 如果一个模板串A有另一个模板串B(也就是B可以覆盖A),那么B是比A更优的一个解
  3. 如果模板串A可以完全覆盖文本串B,那么A在B中的匹配位置(按照开头算)之间的空格数不会超过A的长度

证明:

1.如果不是前后缀的话,那开头和结尾就没法涂了

2.由模板串的定义显然

3.画个图或者看样例,喷涂的时候模板串必须相邻或重叠

由性质1,我们发现满足条件的模板串一定是1~next[n],或next[next[n]],next[next[next[n]]]...这样的子串。

因此我们可以枚举前缀的长度,然后\(O(n)\)判定,但这样的复杂度仍是\(O(n^2)\)的,考虑优化


这时就需要用到fail树。

fail树其实就是把失配的位置连起来得到的一棵树。我们对于\(i \in [1,n]\),连边\((next[i],i)\),\(next[i]\)\(i\)的父亲。由于\(next[i]<i\),连出来的一定是一棵树

显然fail树的根为0,fail树上的节点x表示文本串中1~x位,长度为x的子串。而1~next[n],或next[next[n]],next[next[next[n]]]...这样的子串其实就是fail树上0~next[n]的一条链。

fail树的性质:

y代表的子串的一个公共前后缀为x代表的子串,当且仅当点x是点y的祖先

因此我们就可以先把0~next[n]的链上的节点标记,从根对fail树dfs。每次选在链上的儿子节点递归,然后删除其他儿子节点子树里的所有节点。用一个初始时为1~n的链的双向链表维护相邻元素之间的距离(初始距离为1,删掉x,y中间的一个节点z,x,y之间的距离变为dist(x,z)+dist(y,z))。

容易发现这个距离就是模板串在文本串中的匹配位置之间的空格数.根据模板串的性质3,空格数不会超过模板串的长度。而节点x又表示长度为x的子串,因此递归到$x \geq 空格数 $的时候,x就是最小长度.

由于每个节点最多被删除一次,时间复杂度\(O(n)\)


广告:

这道题思路完全不同的做法

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define maxn 500000
using namespace std;
struct edge{
    int from;
    int to;
    int next;
}E[maxn*2+5];
int sz=1;
int head[maxn+5];
void add_edge(int u,int v){
    sz++;
    E[sz].from=u;
    E[sz].to=v;
    E[sz].next=head[u];
    head[u]=sz;
}

int n;
char s[maxn+5];
int nex[maxn+5];

struct list{
    int pre[maxn+5],nex[maxn+5];
    int mv;
    void ini(int n){
        mv=1;
        for(int i=1;i<=n;i++){
            pre[i]=i-1;
            nex[i]=i+1;
        }
    }
    void del(int x){
        pre[nex[x]]=pre[x];
        nex[pre[x]]=nex[x];
        mv=max(mv,nex[x]-pre[x]);
        pre[x]=nex[x]=0;
        for(int i=head[x];i;i=E[i].next){
            int y=E[i].to;
            del(y);
        }
    }
    inline int query(){
        return mv;
    }
}S;
int ans=0;
bool mark[maxn+5];
void dfs(int x){//x实际上是某个nex,代表前缀(模板串)长度 
    int to;
    if(S.query()<=x){//模板串性质3 
        ans=x;
        return;
    }
    for(int i=head[x];i;i=E[i].next){
        int y=E[i].to;
        if(mark[y]) to=y;
        else S.del(y);//根据fail树的性质2,把不能匹配的部分去掉,得到两个匹配位置之间的最大距离 
    }
    dfs(to);
} 
int main(){
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=2,j=0;i<=n;i++){
        while(j>0&&s[j+1]!=s[i]) j=nex[j];
        if(s[j+1]==s[i]) j++;
        nex[i]=j;
    }
    for(int i=1;i<=n;i++) add_edge(nex[i],i);//建立fail树 
    S.ini(n);
    for(int i=n;i>0;i=nex[i]) mark[i]=1;//模板串性质1 
    dfs(0);
    printf("%d\n",ans);
}

猜你喜欢

转载自www.cnblogs.com/birchtree/p/11768386.html
今日推荐