bzoj1535[POI2005]sza-template

此题解无病呻吟,啰里啰嗦,现已加入零分作文全家桶

这题......坑死我了......

不妨记原串长为i的前缀为prefix(i),next[i]表示prefix(i)的最长公共前后缀长度(不等于prefix(i)自身)(就是MP的next[])

首先,一个前缀必须同时也是原串的后缀才有可能成为答案,否则原串的最后一部分无法覆盖。

所以我们沿着next表往前走,可以找出原串的所有公共前后缀,筛选出可能的答案。

接下来我们验证每个可能的答案。如果用prefix(i)作为模板串,我们可以用KMP算法直接找出prefix(i)在原串中所有的出现位置,这些位置就是prefix(i)可以涂到的位置。接下来把能涂上模板的位置都涂上,判断能否覆盖整个字符串。这相当于找出相邻两个能够涂上模板的位置的最大间隔,看一下这个间隔长度和prefix(i)的长度哪一个大,如果最大间隔比prefix(i)长就说明不能覆盖。

这样暴力做是O(n^2)的。本脑子有坑选手考试连O(n^2)都没推出来,打的O(n^2logn)的->_->

接下来好多人都假定“prefix(i),prefix(j)是两个公共前后缀,i<j,如果prefix(i)是一个可行解,那么prefix(j)一定是可行解,如果prefix(j)不是可行解,那么prefix(i)一定不是可行解”,于是二分答案。但这个题其实没有单调性。比如这个数据:

aabccaabccaabcaabccaabcaabccaabccaabccaabccaabccaabcaabccaabc

prefix(9)是可行解,但prefix(23)不是可行解.....所以二分答案是错的,也可以构造数据卡掉,然而bzoj上好多二分答案过掉的->_->

考虑两个可能的答案prefix(j)和prefix(i),j<i.原串中的某个位置如果能够匹配上prefix(i),那么这个位置一定也能够匹配上prefix(j),但匹配上prefix(j)不一定能够匹配上prefix(i),这启发我们可以按照一定的顺序枚举可能的答案,同时维护所有相邻匹配点的最大间隔。既然题中求的是最小的长度,我们不妨从短到长枚举答案,此时匹配点不断减少。

问题变成:如何快速找出从较短答案变为一个较长答案时减少的匹配点,如何快速完成匹配点的删除并维护最大间隔。

第二个问题,我们可以用一个只带删除操作的链表维护所有匹配点。

这个链表由于只有删除,可以直接用数组实现,pre[i]表示如果点i是匹配点,前一个匹配点是谁;suc[i]表示如果点i是匹配点,下一个匹配点是谁。那么删除i的时候我们用(suc[i]-pre[i])更新最大间隔,并维护suc[]和pre[]数组即可。

第一个问题则需要用到fail树。对1<=i<=n,我们连一条从next[i]到i的边,根节点为0,形成一棵fail树,树中的每个节点i其实代表了原串的一个前缀prefix(i)。fail树中节点i到根节点的路径上包含了prefix(i)的所有公共前后缀。记fail树中以i为根的子树为subtree(i)

fail树的一个性质是:prefix(i)在原串中所有的出现位置(以prefix(i)出现时最后一个字符的下标表示出现位置)都在subtree(i)中

如果prefix(i)的一个出现位置是k,那么prefix(i)就是prefix(k)的后缀,也就是说prefix(i)是prefix(k)的公共前后缀,因此i在k到根节点的路径上,故k在subtree(i)中。

如果k在subtree(i)中,那么i就在k到根节点的路径上,因此prefix(i)是prefix(k)的公共前后缀,故prefix(i)的一个出现位置是k

然后我们发现,从短到长枚举所有可能答案的过程其实是在fail树上从树根朝着节点n走,当前验证节点i时所有可能的匹配点就是subtree(i)中的点,减少的匹配点一定不在subtree(i)中却在subtree(i的父亲)中。因此每走一步我们就用bfs找出subtree(i的父亲)中不属于subtree(i)的点,将它们从链表中删掉即可。

 

#include<cstdio>

#include<cstring>

const int maxn=500005;

char str[maxn];

int next[maxn];

int getnext(){

    int i=1,j=0;next[0]=next[1]=0;

    for(;str[i]!='\0';++i){

        while(j&&str[i]!=str[j])j=next[j];

        if(str[i]==str[j])++j;

        next[i+1]=j;

    }

    return i;

}

int pre[maxn],suc[maxn];

struct edge{

    int to,next;

}lst[maxn];int len=1;

int first[maxn];

void addedge(int a,int b){

    lst[len].to=b;

    lst[len].next=first[a];

    first[a]=len++;

}

int maybe[maxn],tot=0;

int maxgap=1;

void del(int x){

    if(suc[x])pre[suc[x]]=pre[x];

    if(pre[x])suc[pre[x]]=suc[x];

    if(pre[x]!=0&&suc[x]!=0&&suc[x]-pre[x]>maxgap)maxgap=suc[x]-pre[x];

    pre[x]=suc[x]=0;

};

int q[maxn],head,tail;

void bfs(int s,int avoid){

    head=tail=0;

    q[tail++]=s;

    while(head!=tail){

        int x=q[head++];

        if(x==avoid)continue;

        del(x);

        for(int pt=first[x];pt;pt=lst[pt].next){

            q[tail++]=lst[pt].to;

        }

    }

}

int main(){

    scanf("%s",str);

    int n=getnext();

    for(int i=1;i<=n;++i){

        addedge(next[i],i);

    }

    for(int i=n;i;i=next[i])maybe[++tot]=i;

    for(int i=1;i<=n;++i){

        pre[i]=i-1;   

        suc[i]=i+1;

    }

    suc[n]=0;

    for(int i=tot;i>=1;--i){

        bfs(maybe[i+1],maybe[i]);

        if(maxgap<=maybe[i]){

            printf("%d\n",maybe[i]);

            break;

        }

    }

    return 0;

}

 

转载于:https://www.cnblogs.com/liu-runda/p/6013958.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值