zoukankan      html  css  js  c++  java
  • [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);
    }
    
  • 相关阅读:
    【Lintcode】112.Remove Duplicates from Sorted List
    【Lintcode】087.Remove Node in Binary Search Tree
    【Lintcode】011.Search Range in Binary Search Tree
    【Lintcode】095.Validate Binary Search Tree
    【Lintcode】069.Binary Tree Level Order Traversal
    【Lintcode】088.Lowest Common Ancestor
    【Lintcode】094.Binary Tree Maximum Path Sum
    【算法总结】二叉树
    库(静态库和动态库)
    从尾到头打印链表
  • 原文地址:https://www.cnblogs.com/birchtree/p/11768386.html
Copyright © 2011-2022 走看看