后缀数组小结

---------------具体见参考资料.-------------《后缀数组-处理字符串的有力工具 》。

后缀数组三个主要数组:

sa[]--下标为此后缀排名,值为排该名的是誰!

rank[]--下标和值 与 sa数组刚好相反!

height[i]==后缀sa[i] 与后缀 sa[i-1] 的最长公共前缀长度!

常用按height值二分的方法来得到 每两个后缀(因为height只是相邻的后缀的lcp)的最长公共前缀(lcp). 

模板 ::

#define MAXN 20010  
int n,r[MAXN];  //n为输入数组长度,r为输入数组。
int sa[MAXN];  
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];  
int height[MAXN],rank[MAXN];  
inline bool cmp(int *r,int a,int b,int len){  
    return r[a]==r[b]&&r[a+len]==r[b+len];  
}  
void SA(int n,int m){  
    int i,j,p,*x=wa,*y=wb,*t;  
    for(i=0;i<m;i++)  
        wss[i]=0;  
    for(i=0;i<n;i++)  
        wss[x[i]=r[i]]++;  
    for(i=1;i<m;i++)  
        wss[i]+=wss[i-1];  
    for(i=n-1;i>=0;i--)  
        sa[--wss[x[i]]]=i;  
    for(j=p=1;p<n;j<<=1,m=p){  
        for(p=0,i=n-j;i<n;i++)  
            y[p++]=i;  
        for(i=0;i<n;i++){  
            if(sa[i]>=j)  
                y[p++]=sa[i]-j;  
        }  
        for(i=0;i<m;i++)  
            wss[i]=0;  
        for(i=0;i<n;i++)  
            wss[wv[i]=x[y[i]]]++;  
        for(i=1;i<m;i++)  
            wss[i]+=wss[i-1];  
        for(i=n-1;i>=0;i--)  
            sa[--wss[wv[i]]]=y[i];  
        for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)  
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;  
    }  
}  
void Height(int n){  
    int i,j,k=0;  
    for(i=1;i<=n;i++)                                     //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。  
        rank[sa[i]]=i;  
    for(i=0;i<n;height[rank[i++]]=k)  
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);  
}  


应用:

一:单个字符串

1.(可重叠)最长重复子串---因每两个后缀的lcp就是一个子串,所以就是height中的最大值 !

2.(不可重叠)最长重复子串 --就要用二分height来枚举到每两个后缀的lcp,加个长度判断就 ok了!

3.(可重叠)最长重复子串+要出现k次---比1难一点,二分height分组,然后直接求哪个组后缀个数最多就ok 了!因为一个组里的最小也不小于height[i],所以就是个数!

4.不相同子串个数---用n-sa[i] 就得到这么多个子串 ,然后 去掉与前面一个后缀重复的子串,即 n-sa[i]-height[i]

5.求最长回文子串--加一个分隔符在最后,将字符串反写接到后面。然后RMQ预处理height,然后分奇偶枚举每个字符中心,就得到答案了!-详见论文

代码::

#include<cstdio>  
#include<cmath>  
#include<cstring>  
#include<algorithm>  
using namespace std;  
#define MAXN 2010  
int dp[2010][30];  
char r[MAXN],rr[MAXN];  
int sa[MAXN];  
int wa[MAXN],wb[MAXN],wv[MAXN],ws[MAXN];  
int height[MAXN],rk[MAXN];  
inline bool cmp(int *r,int a,int b,int len){  
    return r[a]==r[b]&&r[a+len]==r[b+len];  
}  
void SA(int n,int m){  
    int i,j,p,*x=wa,*y=wb,*t;  
    for(i=0;i<m;i++)  
        ws[i]=0;  
    for(i=0;i<n;i++)  
        ws[x[i]=r[i]]++;  
    for(i=1;i<m;i++)  
        ws[i]+=ws[i-1];  
    for(i=n-1;i>=0;i--)  
        sa[--ws[x[i]]]=i;  
    for(j=p=1;p<n;j<<=1,m=p){  
        for(p=0,i=n-j;i<n;i++)  
            y[p++]=i;  
        for(i=0;i<n;i++){  
            if(sa[i]>=j)  
                y[p++]=sa[i]-j;  
        }  
        for(i=0;i<m;i++)  
            ws[i]=0;  
        for(i=0;i<n;i++)  
            ws[wv[i]=x[y[i]]]++;  
        for(i=1;i<m;i++)  
            ws[i]+=ws[i-1];  
        for(i=n-1;i>=0;i--)  
            sa[--ws[wv[i]]]=y[i];  
        for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)  
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;  
    }  
}  
void Height(int n){  
    int i,j,k=0;  
    for(i=0;i<=n;i++)    //这里sa[0]为‘\0’开始的子串  
        rk[sa[i]]=i;  
    for(i=0;i<n;height[rk[i++]]=k)  
        for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];k++);  
}  
int init(){  
    int i,j,len;  
    memset(height,0,sizeof(height));  
    len=strlen(rr);  
    for(i=0;i<len;i++)  
        r[i]=rr[i];  
    r[i]='$';  
    for(j=0;j<len;j++)  
        r[j+len+1]=r[len-1-j];  
    r[j+len+1]='\0';  
    return len*2+1;  
}  
void st(int n){   //以height的下标分段,每段height最小值保存最小值height对应下标。
    int i,j,p,q;  
    for(i=1;i<=n;i++)  
        dp[i][0]=i;  
    for(j=1;j<=(int)(log((double)n)/log(2.0));j++)  
        for(i=1;i+(1<<j)-1<=n;i++){  
            p=height[dp[i][j-1]];  
            q=height[dp[i+(1<<(j-1))][j-1]];  
            if(p>q)  
                dp[i][j]=dp[i+(1<<(j-1))][j-1];  
            else  
                dp[i][j]=dp[i][j-1];  
        }  
  
}  
int RMQ_MIN(int i,int j){  
    int tem;  
    if(i>j){  
        tem=i;  
        i=j;  
        j=tem;  
    }  
    i++;           //交换后小的要加一  
    int k=(int)(log((double)(j-i+1))/log(2.0));  
    return min(height[dp[i][k]],height[dp[j-(1<<k)+1][k]]);  
}  
void solve(int n){  
    int i,j,ans=0,s;  
    st(n);  
    for(i=0;i<n/2;i++){  
        j=RMQ_MIN(rk[i],rk[n-1-i]);  
        if(j*2-1>ans){   //回文串长度为奇数情况
            ans=j*2-1;  
            s=i-j+1;  
        }  
        j=RMQ_MIN(rk[i],rk[n-i]);  
        if(j*2>ans){  //回文串长度为偶数情况
            ans=j*2;  
            s=i-j;  
        }  
    }  
    for(i=s;i<s+ans;i++)  
        printf("%c",rr[i]);  
    printf("\n");  
}  
int main(){  
    int i,j,n;  
    scanf("%s",rr);  
    n=init();  
    SA(n+1,130);  
    Height(n);  
    solve(n);  
}  

6.连续重复子串

第一种:求一个串最多由几个连续重复串得到。

很简单--看论文。poj2406--用KMP最好!后缀超时了!

第二种:重复次数最多的。poj3963


先不看这图,容易想到这题可以枚举长度下 再每两个(相邻枚举长度)点都枚举下。但这个复杂度为O(n^2)

所以按照 论文说的枚举,解释下那个向前向后匹配,因为像论文那样枚举,那个位置不一定是最佳位置,

就像上图中的 r[6] 与 r[9]  是 aba的中间匹配段,所以这样的话,还要向前匹配。

那么只要l-k%l  就相当于前面可能存在的 匹配段!


#include <iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
#define MAXN 100100
int dp[MAXN][30];
int n,r[MAXN];  //n为输入数组长度,r为输入数组。
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
    return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++)
        wss[i]=0;
    for(i=0;i<n;i++)
        wss[x[i]=r[i]]++;
    for(i=1;i<m;i++)
        wss[i]+=wss[i-1];
    for(i=n-1;i>=0;i--)
        sa[--wss[x[i]]]=i;
    for(j=p=1;p<n;j<<=1,m=p){
        for(p=0,i=n-j;i<n;i++)
            y[p++]=i;
        for(i=0;i<n;i++){
            if(sa[i]>=j)
                y[p++]=sa[i]-j;
        }
        for(i=0;i<m;i++)
            wss[i]=0;
        for(i=0;i<n;i++)
            wss[wv[i]=x[y[i]]]++;
        for(i=1;i<m;i++)
            wss[i]+=wss[i-1];
        for(i=n-1;i>=0;i--)
            sa[--wss[wv[i]]]=y[i];
        for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
void Height(int n){
    int i,j,k=0;
    for(i=1;i<=n;i++)                                     //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。
        rank[sa[i]]=i;
    for(i=0;i<n;height[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
int get_min(int a,int b)
{
    return a<b?a:b;
}
void rmq(int n){   //以height的下标分段,每段height最小值保存最小值height对应下标。
    int i,j,p,q;
    for(i=1;i<=n;i++)
        dp[i][0]=i;
    for(j=1;j<=(int)(log((double)n)/log(2.0));j++)
        for(i=1;i+(1<<j)-1<=n;i++){
            p=height[dp[i][j-1]];
            q=height[dp[i+(1<<(j-1))][j-1]];
            if(p>q)
                dp[i][j]=dp[i+(1<<(j-1))][j-1];
            else
                dp[i][j]=dp[i][j-1];
        }

}
int lcp(int i,int j){
    int tem;
    if(i>j){
        tem=i;
        i=j;
        j=tem;
    }
    i++;           //交换后小的要加一
    int k=(int)(log((double)(j-i+1))/log(2.0));
    return min(height[dp[i][k]],height[dp[j-(1<<k)+1][k]]);
}
char op[MAXN];
int cas=0;
int ans[MAXN];
void solve(int n)
{
    int k,step,t,l,cnt,maxx=-1;
    for(l=1;l<n;l++)
    {
        for(int i=0;i+l<n;i+=l)//枚举位置
        {
            if(r[i]==r[i+l])
            {
                k=lcp(rank[i],rank[i+l]);
            step=k/l+1;
            t=i-(l-k%l);
            if(t>=0&&k%l)
            {
                if(lcp(rank[t],rank[t+l])>=k) step++;
            }
            if(step>maxx)
            {
                maxx=step;
                cnt=0;
                ans[cnt++]=l;
            }
            if(step==maxx) ans[cnt++]=l;//因为同样重复次数下这个长度下,字典序可能更小!
            }

        }
    }
    int start;
    for(int i=1;i<n;i++)//枚举名次
    {
        int k=sa[i];//枚举名次,就可以保证 字典序最小!
        for(int j=0;j<cnt;j++)
        {
            int ll=ans[j];
            if(lcp(rank[k],rank[k+ll])>=(maxx-1)*ll)
            {
                start=k;
                l=maxx*ll;
                i=n;
                break;
            }
        }
    }
    printf("Case %d: ",++cas);
    for(int i=0;i<l;i++) printf("%c",op[start+i]);
    printf("\n");
}
int main()
{
    while(~scanf("%s",op)&&op[0]!='#')
    {
        int len=strlen(op);
        for(int i=0;i<len;i++) r[i]=op[i]-'a'+1;
        r[len]=0;
        SA(len+1,30);
        Height(len);
        rmq(len);
        solve(len);
    }
    return 0;
}

  




二.两个字符串

1.最长公共子串--就连起两个串,求相邻SA在不同串的height最大的值就好了。

代码::

#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 2000050
int n,r[MAXN];  //n为输入数组长度,r为输入数组。
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
    return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++)
        wss[i]=0;
    for(i=0;i<n;i++)
        wss[x[i]=r[i]]++;
    for(i=1;i<m;i++)
        wss[i]+=wss[i-1];
    for(i=n-1;i>=0;i--)
        sa[--wss[x[i]]]=i;
    for(j=p=1;p<n;j<<=1,m=p){
        for(p=0,i=n-j;i<n;i++)
            y[p++]=i;
        for(i=0;i<n;i++){
            if(sa[i]>=j)
                y[p++]=sa[i]-j;
        }
        for(i=0;i<m;i++)
            wss[i]=0;
        for(i=0;i<n;i++)
            wss[wv[i]=x[y[i]]]++;
        for(i=1;i<m;i++)
            wss[i]+=wss[i-1];
        for(i=n-1;i>=0;i--)
            sa[--wss[wv[i]]]=y[i];
        for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
void Height(int n){
    memset(height,0,sizeof(height));
    int i,j,k=0;
    for(i=1;i<=n;i++)                                     //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。
        rank[sa[i]]=i;
    for(i=0;i<n;height[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
char op[MAXN];
int solve(int mid)
{
    int ans=0;
   for(int i=1;i<=n;i++) //遍历一遍height
   {
       if((sa[i]<mid&&sa[i-1]>mid)||(sa[i-1]<mid&&sa[i]>mid))//判断是否是两个串的后缀,而不是同一个串的后缀的LCP。
        if(height[i] > ans) ans=height[i]; //找最大值就是答案了!
   }
   return ans;
}
int main()
{
    while(~scanf("%s",op))
    {
        int len=strlen(op);
        op[len]='0';
        scanf("%s",op+len+1);
        n=strlen(op);
        op[n]='0';
        for(int i=0;i<n;i++) r[i]=op[i]-'a'+50;
        SA(n+1,80);
        Height(n);
        printf("%d\n",solve(len));
    }
    return 0;
}

2.长度大于k的 公共子串个数。(位置不同的相同子串 答案要+1)

解题思路:若用height分组,然后枚举每个A与一个B子串,B与每一个A子串,就是O(N^2)超时!

那么这里用到了一个 单调栈   来维护一下最小值--(因为每两个后缀的LCP是一段height的最小值);

用栈维护的话能够减少复杂度就是因为 --- 它能够把排名大于B该子串的前面一段中 所有的A串的子串与它的

LCP全部加起来!

然后再对A 做一次相同操作---因为还有A串排名比B串小的,也要算进来!

思想就是如此,具体见代码了!

代码:

#include <stdio.h>
#include <string.h>
#define MAX 200100
int k,n,top;
int len,len1,len2;
__int64 tot,ans,num[MAX];
int st[MAX],arr[MAX];
int wa[MAX],wb[MAX];
int wv[MAX],wn[MAX];
int sa[MAX],rank[MAX],h[MAX];
int cmp(int *r,int a,int b,int l) {
	
	return r[a] == r[b] && r[a+l] == r[b+l];
}
void Da(int *r,int n,int m) {
	
	int i,j,k,p,*t;
	int *x = wa,*y = wb;
	for (i = 0; i < m; ++i) wn[i] = 0;
	for (i = 0; i < n; ++i) wn[x[i]=r[i]]++;
	for (i = 1; i < m; ++i) wn[i] += wn[i-1];
	for (i = n - 1; i >= 0; --i) sa[--wn[x[i]]] = i;
	for (j = 1,p = 1; p < n; j *= 2,m = p) {
		
		for (p = 0,i = n - j; i < n; ++i) y[p++] = i;
		for (i = 0; i < n; ++i) if (sa[i] >= j) y[p++] = sa[i] - j;
		for (i = 0; i < n; ++i) wv[i] = x[y[i]];
		for (i = 0; i < m; ++i) wn[i] = 0;
		for (i = 0; i < n; ++i) wn[wv[i]]++;
		for (i = 1; i < m; ++i) wn[i] += wn[i-1];
		for (i = n - 1; i >= 0; --i) sa[--wn[wv[i]]] = y[i];	
		t = x,x = y,y = t,p = 1;
		for (x[sa[0]] = 0,i = 1; i < n; ++i)
			x[sa[i]] = cmp(y,sa[i-1],sa[i],j) ? p - 1 : p++;
	}
}
void CalHeight(int *r,int n) {
	
	int i,j,k = 0;
	for (i = 1; i <= n; ++i) rank[sa[i]] = i;
	for (i = 0; i < n; h[rank[i++]] = k)
		for (k ? k-- : 0,j = sa[rank[i]-1]; r[i+k] == r[j+k];k++);
}
__int64 Solve(int n,int k) {
	__int64 i,j,tp,ans = 0;
	for (i = 1; i <= n; ++i) {				//单调栈处理
		if (h[i] < k) tot = top = 0;		//分组,小于k的就分为一组,不多做处理
		else {
			tp = 0;							//默认tp = 0
			if (sa[i-1] > len1)				//如果前面一段是字符串B
				tp = 1,tot += h[i] - k + 1; //tp是累计可增加的贡献值
			while (top > 0 && st[top] >= h[i]) {

				tot -= num[top] * (st[top] - h[i]);
				tp += num[top],top--;
			}
			st[++top] = h[i],num[top] = tp;
			if (sa[i] < len1) ans += tot;
		}
	}
	for (i = 1; i <= n; ++i) { //单调栈处理
		
		if (h[i] < k) tot = top = 0;
		else {
			tp = 0;
			if (sa[i-1] < len1)
				tp = 1,tot += h[i] - k + 1;
			while (top > 0 && st[top] >= h[i]) {
				
				tot -= num[top] * (st[top] - h[i]);
				tp += num[top],top--;
			}
			st[++top] = h[i],num[top] = tp;
			if (sa[i] > len1) ans += tot;
		}
	}
	return ans;
}
int main()
{
	int i,j,t,cas = 0;
	char str1[MAX],str2[MAX];
	while (scanf("%d",&k),k) {	
		scanf("%s%s",str1,str2);
		for (i = 0; str1[i]; ++i)
			arr[i] = str1[i];
		arr[i] = '$',len1 = i,i++;
		for (j = 0; str2[j];j++)
			arr[i+j] = str2[j];
		arr[i+j] = 0,len = i + j;	
		Da(arr,len+1,150);
		CalHeight(arr,len);		
		ans = Solve(len,k);
		printf("%I64d\n",ans);
	}
}


三.多个字符串

1.poj3294-出现在超过半数字符串 中的 最长公共子串。

这题目非常考细节,首先RE了很多次,后来又TLE,所以总结下经验:

第一:不要用数组存(可能会改变的)中间结果,特别是当数据特别大时。

就像代码中的二分分组中,只需要判断这个mid满不满足。而不要去存每个mid下的ans。

因为有可能存不下! 只需要得到最佳mid,然后再去求一遍ans就可以了!

第二:后缀数组的离散化数字m的值不可以大于227,其实只有小写字母的话,只要26+n个就行了

这个n就是要用到的 各自互相不同的分隔符的个数。

第三:memset() 不要去更新很大的数组,会超时!

第四:这题目要注意n=1,就直接输出就ok了!



//3688K	1172MS	AC 2014-04-07 11:37:52
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 101000
int T;
int n,r[MAXN];  //n为输入数组长度,r为输入数组。
char op[1010];
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
    return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++)
        wss[i]=0;
    for(i=0;i<n;i++)
        wss[x[i]=r[i]]++;
    for(i=1;i<m;i++)
        wss[i]+=wss[i-1];
    for(i=n-1;i>=0;i--)
        sa[--wss[x[i]]]=i;
    for(j=p=1;p<n;j<<=1,m=p){
        for(p=0,i=n-j;i<n;i++)
            y[p++]=i;
        for(i=0;i<n;i++){
            if(sa[i]>=j)
                y[p++]=sa[i]-j;
        }
        for(i=0;i<m;i++)
            wss[i]=0;
        for(i=0;i<n;i++)
            wss[wv[i]=x[y[i]]]++;
        for(i=1;i<m;i++)
            wss[i]+=wss[i-1];
        for(i=n-1;i>=0;i--)
            sa[--wss[wv[i]]]=y[i];
        for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
void Height(int n){
    int i,j,k=0;
    for(i=1;i<=n;i++)
        rank[sa[i]]=i;
    for(i=0;i<n;height[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
int cas;
int flag[MAXN];
int vis[200];
int ok(int pos)
{

    if(vis[flag[pos]]==1) {return false;}
    else {vis[flag[pos]]=1;return true;}
}
int judge(int key,int n)
{
    memset(vis,0,sizeof(vis));
    int tot=0;
    for(int i=2;i<=n;i++)
    {
        if(height[i]<key)
        {
            memset(vis,0,sizeof(vis));
            tot=0;
        }
        else
        {
            if(ok(sa[i-1])) tot++;
            if(ok(sa[i])) tot++;
            if(tot>(T/2)) return 1;
        }
    }
    return 0;
}
void OutAns(int key,int n)
{
    memset(vis,0,sizeof(vis));
    int tot=0;
    for(int i=1;i<=n;i++)
    {
        if(height[i]<key)
        {
            memset(vis,0,sizeof(vis));
            if(tot>(T/2))
            {
                for(int j=sa[i-1];j<sa[i-1]+key;j++)
                    printf("%c",r[j]+'a'-1);
                printf("\n");
            }
            tot=0;
        }
        else
        {
            if(ok(sa[i-1])) tot++;
            if(ok(sa[i])) tot++;
        }
    }
}
void solve(int n)
{
    cas=0;
    int l,r,mid,flag=0,ans;
    l=1;r=1010;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(judge(mid,n)==1)
        {
            ans=mid;
            l=mid+1;
            flag=1;
        }
        else r=mid-1;
    }
    if(flag)
    OutAns(ans,n);
    else printf("?\n");
}

int main()
{
    int flag1=1;
    while(~scanf("%d",&T)&&T)
    {
        int len=-1,sumlen=0,m=28;

        for(int i=0;i<T;i++)
        {
            scanf("%s",op);
            len=strlen(op);
            for(int j=0;j<len;j++)
            {
                r[sumlen]=op[j]-'a'+1;
                flag[sumlen++]=i;
            }
            r[sumlen]=m;
            flag[sumlen++]=m++;
        }
        cout<<sumlen<<endl;
        r[sumlen]=0;
        if(flag1){flag1=0;}
        else if(!flag1) {printf("\n");}
        if(T==1) {op[len]='\0';printf("%s\n",op);continue;}
        SA(sumlen+1,m);
        Height(sumlen);
        solve(sumlen);

    }
    return 0;
}


2.spoj--给定n个字符串,求在每个字符串中出现两次且 不重叠 的最长子串!

分析:有了上一题的经验,这一题就简单了!

这里要判断这么几个条件:

1。这个子串在所有串中都出现 即tot记录等于N。

2。要出现两次--其实你去判断不重叠时就已经把这个条件包括进去了!

3。不重叠--就是要位置相减不小于枚举的子串长,这个前面的单个字符串就做过了!

那么首先每枚举一个位置,就先判断它所在的串是否已经满足过条件了!

前者没满足前提下,那么这个串就有以下几个情况:

                                                                     1.第一次存进来的串。for循环没有执行,直接保存了!为了与后串比较!

                                                                      2.后面存进来的串 也有几个情况:

                        1.前面已经出现过的!直接return 0;

2.前面没出现过,那么一一比较存在满足条件的直接return 1;否则保存下来!


#include <iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
#define MAXN 110000
int n,r[MAXN];  //n为输入数组长度,r为输入数组。
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
    return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++)
        wss[i]=0;
    for(i=0;i<n;i++)
        wss[x[i]=r[i]]++;
    for(i=1;i<m;i++)
        wss[i]+=wss[i-1];
    for(i=n-1;i>=0;i--)
        sa[--wss[x[i]]]=i;
    for(j=p=1;p<n;j<<=1,m=p){
        for(p=0,i=n-j;i<n;i++)
            y[p++]=i;
        for(i=0;i<n;i++){
            if(sa[i]>=j)
                y[p++]=sa[i]-j;
        }
        for(i=0;i<m;i++)
            wss[i]=0;
        for(i=0;i<n;i++)
            wss[wv[i]=x[y[i]]]++;
        for(i=1;i<m;i++)
            wss[i]+=wss[i-1];
        for(i=n-1;i>=0;i--)
            sa[--wss[wv[i]]]=y[i];
        for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
void Height(int n){
    int i,j,k=0;
    for(i=1;i<=n;i++)                                     //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。
        rank[sa[i]]=i;
    for(i=0;i<n;height[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
char op[10010];
int flag[MAXN];
int vis[20];//判断是否已经满足条件!
int save[20][10010];//保存每个字符串下这些pos位置,以便进行寻找!
int lenth[20];//每个字符串的保存了的pos有几个!
int N;//输入字符串个数
int ok(int pos,int key)
{
    if(vis[flag[pos]]==1) return 0;
    for(int i=1;i<=lenth[flag[pos]];i++)
    {
        if(save[flag[pos]][i]==pos) return 0;
        else if(fabs(save[flag[pos]][i]-pos)>=key) {vis[flag[pos]]=1;return 1;}
    }
    save[flag[pos]][ ++lenth[flag[pos]] ]=pos;
    return 0;
}
int judge(int key,int n)
{
    int tot=0;
    memset(vis,0,sizeof(vis));
    memset(lenth,0,sizeof(lenth));
    for(int i=1;i<=n;i++)
    {
        if(height[i]<key)
        {
            memset(vis,0,sizeof(vis));
            memset(lenth,0,sizeof(lenth));
            tot=0;
        }
        else
        {
            if(ok(sa[i-1],key)) tot++;
            if(ok(sa[i],key)) tot++;
            if(tot==N) return 1;
        }
    }
    return 0;
}
void solve(int n)
{
    int mid,l=1,r=10000;
    int ans=0;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(judge(mid,n)==1) {ans=mid;l=mid+1;}
        else r=mid-1;
    }
    printf("%d\n",ans);
}
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
         int len,sumlen=0,m=28;
        scanf("%d",&N);
        for(int i=0;i<N;i++)
        {
            scanf("%s",op);
            len=strlen(op);
            for(int j=0;j<len;j++)
            {
                r[sumlen]=op[j]-'a'+1;
                flag[sumlen++]=i;
            }
            r[sumlen]=m;
            r[sumlen++]=m++;
        }
        r[sumlen]=0;
        SA(sumlen+1,m);
        Height(sumlen);
        solve(sumlen);
    }
    return 0;
}


3.pok1226

就把正向的反向的串都连起来,判断下就ok!

//Accepted	508K	16MS	C++	2891B	
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAXN 202000
int n,r[MAXN];  //n为输入数组长度,r为输入数组。
int sa[MAXN];
int wa[MAXN],wb[MAXN],wv[MAXN],wss[MAXN];
int height[MAXN],rank[MAXN];
inline bool cmp(int *r,int a,int b,int len){
    return r[a]==r[b]&&r[a+len]==r[b+len];
}
void SA(int n,int m){
    int i,j,p,*x=wa,*y=wb,*t;
    for(i=0;i<m;i++)
        wss[i]=0;
    for(i=0;i<n;i++)
        wss[x[i]=r[i]]++;
    for(i=1;i<m;i++)
        wss[i]+=wss[i-1];
    for(i=n-1;i>=0;i--)
        sa[--wss[x[i]]]=i;
    for(j=p=1;p<n;j<<=1,m=p){
        for(p=0,i=n-j;i<n;i++)
            y[p++]=i;
        for(i=0;i<n;i++){
            if(sa[i]>=j)
                y[p++]=sa[i]-j;
        }
        for(i=0;i<m;i++)
            wss[i]=0;
        for(i=0;i<n;i++)
            wss[wv[i]=x[y[i]]]++;
        for(i=1;i<m;i++)
            wss[i]+=wss[i-1];
        for(i=n-1;i>=0;i--)
            sa[--wss[wv[i]]]=y[i];
        for(t=x,x=y,y=t,x[sa[0]]=0,p=i=1;i<n;i++)
            x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    }
}
void Height(int n){
    int i,j,k=0;
    for(i=1;i<=n;i++)                                     //注意sa[0]是以r[n-1]开始的串即0,所以这里忽略sa[0]。
        rank[sa[i]]=i;
    for(i=0;i<n;height[rank[i++]]=k)
        for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
char op[200];
int vis[200];
int flag[MAXN];
int N;
int ok(int pos)
{
    if(vis[flag[pos]]) {return 0;}
    else {vis[flag[pos]]=1;return 1;}
}
int judge(int key,int n)
{
    int tot=0;
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
    {
        if(height[i]<key)
        {
            memset(vis,0,sizeof(vis));
            tot=0;
        }
        else
        {
            if(ok(sa[i-1])) tot++;
            if(ok(sa[i])) tot++;
            if(tot==N) return 1;
        }
    }
    return 0;
}

void solve(int n)
{
    int l=1,r=100,ans=0;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(judge(mid,n)) {ans=mid;l=mid+1;}
        else {r=mid-1;}
    }
    printf("%d\n",ans);
}
int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        int sumlen=0,m=140;
        scanf("%d",&N);
        for(int i=0;i<N;i++)
        {
            scanf("%s",op);
            int len=strlen(op);
            for(int j=0;j<len;j++)
            {
                r[sumlen]=op[j];
                flag[sumlen++]=i;
            }
            r[sumlen]=m;
            flag[sumlen++]=m++;
            for(int j=len-1;j>=0;j--)
            {
                r[sumlen]=op[j];
                flag[sumlen++]=i;
            }
            r[sumlen]=m;
            flag[sumlen++]=m++;
        }
        //if(N==1) {printf("%s\n",op);continue;}
        r[sumlen]=0;
        SA(sumlen+1,m);
        Height(sumlen);
        solve(sumlen);
    }
    return 0;
}









1. 用户与权限管理模块 角色管理: 学生:查看实验室信息、预约设备、提交耗材申请、参与安全考核 教师:管理课题组预约、审批学生耗材申请、查看本课题组使用记录 管理员:设备全生命周期管理、审核预约、耗材采购与分发、安全检查 用户操作: 登录认证:统身份认证(对接学号 / 工号系统,模拟实现),支持密码重置 信息管理:学生 / 教师维护个人信息(联系方式、所属院系),管理员管理所有用户 权限控制:不同角色仅可见对应功能(如学生不可删除设备信息) 2. 实验室与设备管理模块 实验室信息管理: 基础信息:实验室编号、名称、位置、容纳人数、开放时间、负责人 功能分类:按学科(计算机实验室 / 电子实验室 / 化学实验室)标记,关联可开展实验类型 状态展示:实时显示当前使用人数、设备运行状态(正常 / 故障) 设备管理: 设备档案:名称、型号、规格、购置日期、单价、生产厂家、存放位置、责任人 全生命周期管理: 入库登记:管理员录入新设备信息,生成唯资产编号 维护记录:记录维修、校准、保养信息(时间、内容、执行人) 报废处理:登记报废原因、时间,更新设备状态为 "已报废" 设备查询:支持按名称、型号、状态多条件检索,显示设备当前可用情况 3. 预约与使用模块 预约管理: 预约规则:学生可预约未来 7 天内的设备 / 实验室,单次最长 4 小时(可设置) 预约流程:选择实验室→选择设备→选择时间段→提交申请(需填写实验目的) 审核机制:普通实验自动通过,高危实验(如化学实验)需教师审核 使用记录: 签到 / 签退:到达实验室后扫码签到,离开时签退,系统自动记录实际使用时长 使用登记:填写实验内容、设备运行情况(正常 / 异常),异常情况需详细描述 违规管理:迟到 15 分钟自动取消预约,多次违规限制预约权限 4. 耗材与安全管理模块 耗材管理: 耗材档案:名称、规格、数量、存放位置、
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值