• [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
    ",ans);
    }
    
  • 相关阅读:
    三点求圆心坐标(三角形外心)
    半平面交
    旋转卡壳
    平面最近点对(HDU 1007)
    凸包
    ACM做题随做随思
    最短路径——SPFA算法
    树链剖分原理
    生成树的计数——Matrix-Tree定理
    次小生成树
  • 原文地址:https://www.cnblogs.com/birchtree/p/11768386.html
Copyright © 2020-2023  润新知