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.})

  • 相关阅读:
    HDU4628+状态压缩DP
    Javascript 去掉字符串前后空格的五种方法
    Javascript 数组之判断取值和数组取值
    ASP.NET MVC 出现错误 “The view 'XXX' or its master was not found or no view engine support”
    ASP.NET MVC 页面调整并传递参数
    ASP.NET MV3 部署网站 报"Could not load file or assembly ' System.Web.Helpers “ 错的解决方法
    ASP.NET MVC 控制器向View传值的三种方法
    CSharp 如何通过拼接XML调用存储过程来查询数据
    SQLServer : EXEC和sp_executesql的区别
    关于SQLServer2005的学习笔记—异常捕获及处理
  • 原文地址:https://www.cnblogs.com/h-lka/p/13194066.html
Copyright © 2011-2022 走看看