• 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]!='';++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
    ",maybe[i]);
    
                break;
    
            }
    
        }
    
        return 0;
    
    }
  • 相关阅读:
    MySQL重置密码
    linux下自动备份脚本并上传到ftp服务器
    nginx配置
    WIFI防蹭网
    无线路由知识
    009汇编环境搭建
    008 计算机不会加法
    007计算机不会做加法
    006源码反码补码
    005有符号数和无符号数
  • 原文地址:https://www.cnblogs.com/liu-runda/p/6013958.html
Copyright © 2020-2023  润新知