zoukankan      html  css  js  c++  java
  • 【题解】SP1811 LCS

    (color{purple}{Link})

    ( ext{Solution:})

    题目要求找到两个串的最长公共子串。(LCP)

    我们将两个串中间和末尾插入终止符,并弄到一棵后缀树上去。

    然后我们发现,对于一个叶子节点,它属于哪个子串,我们只需要找到它的父边上第一个出现的终止符属于哪个边即可。

    这里,我们可以用个奇技淫巧——前缀和实现。

    介于( ext{Suffix Tree})的边都是压缩的,所以维护信息变得不是很容易,所以可以采用一个在插入外面进行预处理前缀和的方式维护。

    然后,我们只需要找到一个最深的非叶子节点,使得它的子树中既含有第一个串串的终止符,也含有第二个串串的终止符即可。

    此时它的答案就是这个点的深度。

    ( ext{Suffix Tree})的主要问题就在于边上信息的维护。如果找不到一个好的方法去维护,( ext{Suffix Tree})还是很麻烦的。

    最近做题,题解区域都没有( ext{Suffix Tree})的题解,做起来真的挺累……我太菜了。

    #include<bits/stdc++.h>
    #include<ctime>
    using namespace std;
    const int MAXN=1.2e6+10;
    string Z,z;
    int n,val[MAXN],ans,tot;
    int sum[MAXN],sum2[MAXN];
    const int inf=(1<<30);
    struct SuffixTree {
    	int link[MAXN],ch[MAXN][28],now,rem,n;
    	int start[MAXN],len[MAXN],tail,s[MAXN];
    	SuffixTree() {
    		tail=now=1;
    		rem=n=0;
    		len[0]=inf;
    	}
    	inline int build(int a,int b) {
    		link[++tail]=1;
    		start[tail]=a;
    		len[tail]=b;
    		return tail;
    	}
    	void Extend(int x) {
    		s[++n]=x;
    		++rem;
    		for(int last=1; rem;) {
    			while(rem>len[ch[now][s[n-rem+1]]])
    				rem-=len[now=ch[now][s[n-rem+1]]];
    			int &v=ch[now][s[n-rem+1]];
    			int c=s[start[v]+rem-1];
    			if(!v||x==c) {
    				link[last]=now;
    				last=now;
    				if(!v)v=build(n,inf);
    				else break;
    			} else {
    				int u=build(start[v],rem-1);
    				ch[u][c]=v;
    				ch[u][x]=build(n,inf);
    				start[v]+=rem-1;
    				len[v]-=rem-1;
    				link[last]=v=u;
    				last=u;
    			}
    			if(now==1)--rem;
    			else now=link[now];
    		}
    	}
    } T;
    void predfs(int u,int dep) {
    	if(dep>=inf) {
    		int L=T.start[u];
    		int R=L+T.len[u]-1;
    		R=min(R,T.n);
    		int V=sum[R]-sum[L-1];
    		if(V)val[u]=1;
    		else{
    			V=sum2[R]-sum2[L-1];
    			if(V)val[u]=2;
    		}
    		return;
    	}
    	for(int i=0; i<28; ++i) {
    		if(T.ch[u][i]) {
    			predfs(T.ch[u][i],dep+T.len[T.ch[u][i]]);
    			val[u]|=val[T.ch[u][i]];
    		}
    	}
    	if(val[u]>=3)ans=max(ans,dep);
    }
    char buf[1<<21],*p1=buf,*p2=buf;
    string read(){
    	#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    	string s="";
    	char ch=gc();
    	while(ch=='
    ')ch=gc();
    	while(ch!='
    ')s+=ch,ch=gc();
    	return s;
    }
    int main() {
    //	freopen("1.in","r",stdin);
    //	freopen("SP.out","w",stdout);
    	clock_t ST,ET;
    	ST=clock();
    	z=read();Z=read();
    	z+=(char)'a'+26;
    	z+=Z;z+=(char)'a'+27 ;
    	for(int i=0;i<z.size();++i)z[i]-='a',T.Extend(z[i]);
    	tot=z.size();
    	for(int i=1; i<=tot; ++i) {
    		sum[i]=sum[i-1]+(T.s[i]==26);
    		sum2[i]=sum2[i-1]+(T.s[i]==27);
    	}
    	predfs(1,0);
    	printf("%d
    ",ans);
    	ET=clock();
    //	cout<<(double)(ET-ST)/CLOCKS_PER_SEC<<"s"<<endl;
    	return 0;
    }
    

    注意,如果在(dfs)里面来根据这条边的起点和终点暴力处理的话,这就是个(n^2)暴力。观察到,我们只需要在叶子的节点处理,而在叶子节点暴力处理的复杂度也不够优秀。

    观察到,第一个字符串的终止符一定先于第二个字符串的终止符出现(如果有的话)。那么,根据前缀和,先判断第一个终止符,再判断第二个终止符即可。

    最后时间复杂度是:

    ( ext{We let D show the constant,then the complexity is O(D*N).N is the length of these strings.})

  • 相关阅读:
    一生要做的99件事
    Flash 简单的Mouse.hide()
    [转]FCKeditor
    [转]pv是什么意思?什么是pv值,pv访问量?网站pv是什么?
    CSS:用DIV+CSS实现表格形式的数据排列
    css "Float"
    Jemin的div+css测试文件
    原来
    我终于知道什么情况下得用table了
    转的 关于div 的重叠
  • 原文地址:https://www.cnblogs.com/h-lka/p/13194066.html
Copyright © 2011-2022 走看看