后缀数组java代码_后缀数组专题与代码模板

因为 height[i] 表示 suffix( sa[i-1] ) 与 suffix( sa[i] )的 LCP, 我们统计出所有连续长度为K中的最小LCP,然后对所有最小LCP取最大值即为答案。这里可以用单调队列维护。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include#include

using namespacestd;const int N = (int)2e5+10;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int *r,int *sa,int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa, intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}intQ[N], sa[N];void solve(int *r,int n,int m,intK)

{

da(r,sa,n+1,m);

calheight(r,sa,n);int ql = 0, qr = 0, Max = 0;for(int i = 2;i <= n; i++)

{while( (qr>ql) && ( height[Q[qr-1]] >= height[i] ) ) qr--;

Q[qr++] =i;if( i > K+1 ) while( (ql= K+1 ) Max =max( Max, height[Q[ql]] );

}

printf("%d\n", Max);

}intr[N], n, K;inttmp[N];

mapmp;intmain(){while( scanf("%d%d",&n,&K) !=EOF)

{for(int i = 0; i < n; i++)

{

scanf("%d",&r[i]);

tmp[i]=r[i];

}if( n == 1 && K == 1 ){ puts("1"); continue; }

sort(tmp,tmp+n);int sz = unique(tmp,tmp+n)-tmp;

mp.clear();for(int i = 0;i < sz; i++)

mp[ tmp[i] ]= i+1;for(int i = 0; i < n; i++)

r[i]=mp[ r[i] ];

r[n]= 0;

solve( r, n, sz+1, K-1);

}return 0;

}

View Code

子串的个数

spoj 694 不相同的子串个数,可以重叠。

一个串S,其所有子串 Si, 必定是某个后缀 Suffix(k)的一个前缀。 我们依据此将问题转换成求所有后缀的,不相同前缀数量。

将所有后缀按照  suffix( sa[1] ), suffix( sa[2] ), ..., suffix( sa[n] ) 依次放入然后计算。

当一个串 S(有效范围 [1,n]), 则当一个后缀 suffix( sa[i] ) 加入进来, 其会产生 n-sa[i]+1 个前缀,并且会有height[i]个与前一个相同。

所以加入当前后缀增加的不同前缀数量为:  n-sa[i]+1 - height[i].   然后整个串的不同子串数量即为,所有前缀产生的总和。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include

using namespacestd;const int N = 1010;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int *r,int *sa,int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa, intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}charstr[N];intsa[N], n, r[N];voidsolve()

{

da(r,sa,n+1,128);

calheight(r,sa,n);int ans = 0;for(int i = 1; i <= n; i++)

ans+= n-sa[i]-height[i];

printf("%d\n", ans );

}intmain(){intT;

scanf("%d",&T);while( T--)

{

scanf("%s", str);

n=strlen(str);for(int i = 0; i < n; i++)

r[i]= (int)str[i];

r[n]=0;

solve();

}return 0;

}

View Code

spoj 705 如上题一样,只是N扩大到了10^5, 上题N=1000,可以用DP来做

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include

using namespacestd;const int N = 51010;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int *r,int *sa,int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa, intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}charstr[N];intsa[N], n, r[N];voidsolve()

{

da(r,sa,n+1,128);

calheight(r,sa,n);int ans = 0;for(int i = 1; i <= n; i++)

ans+= n-sa[i]-height[i];

printf("%d\n", ans );

}intmain(){intT;

scanf("%d",&T);while( T--)

{

scanf("%s", str);

n=strlen(str);for(int i = 0; i < n; i++)

r[i]= (int)str[i];

r[n]=0;

solve();

}return 0;

}

View Code

回文子串

URAL 1297 将原串S,与倒序串S1,连接起来,连接出插入一特殊字符。

分奇数和偶数枚举对称中心, 对于奇数 Lcp( rank[i], rank[ 2*n-i ] ). 对于偶数 Lcp( rank[i], rank[2*n+1-i] )

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include#include

using namespacestd;const int N = 2024;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int *r,int *sa,int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa, intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}charstr[N];intr[N], sa[N], n;int dp[N][20];voidInitRMQ(){int cnt = n+n+1;for(int i = 1; i <= cnt; i++)

dp[i][0] =height[i];for(int i = 1; (1<

{for(int j = 1; j+(1<

dp[j][i]= min( dp[j][i-1], dp[j+(1<

}

}int query(int L,intR){if(L > R) swap(L,R); L++;int k = (int)floor( log(1.*(R-L+1)) / log(2.0) );int t = min( dp[L][k], dp[R-(1<

}voidsolve(){int cnt = n+n+1;

da(r,sa,cnt+1,128);

calheight(r,sa,cnt);/*printf("n = %d\n", n );

for(int i = 1; i <= cnt; i++)

printf("height[%d] = %d\n", i, height[i]);

for(int i = 0; i <= cnt; i++)

printf("rank[%d] = %d, sa[%d] = %d\n", i,rank[i],i,sa[i] );*/InitRMQ();int Max1 = 0, p1;//odd

for(int i = 0; i < n; i++){int t = query( rank[i], rank[n+n-i] );if( t > Max1 ) Max1 = t, p1 =i;

}int Max2 = 0, p2;//even

for(int i = 1;i < n; i++){int t = query( rank[i], rank[n+n-i+1] );if( t > Max2 ) Max2 = t, p2 =i;

}//printf("Max1=%d,p1=%d, Max2=%d,p2=%d\n",Max1,p1,Max2,p2);

if( 2*Max1-1 >= 2*Max2 ){for(int j = p1-Max1+1; j <= p1+Max1-1; j++)

printf("%c",str[j]); puts("");

}else{for(int j = p2-Max2; j <= p2+Max2-1; j++)

printf("%c", str[j]); puts("");

}

}intmain(){//printf("%lf\n", log(5.0)/log(2.0) );

while( scanf("%s",str) !=EOF)

{

n=strlen(str);for(int i = 0; i < n; i++)

r[i]= (int)str[i];

r[n]= 1;int t =n;for(int i = n-1; i >= 0; i--)

r[++t] = (int)str[i];

r[2*n+1] = 0;//for(int i = 0; i <= 2*n; i++)//printf("r[%d] = %d\n", i, r[i] );

solve();

}return 0;

}

View Code

连续重复子串

poj 2406 虽然此题用KMP的nxt函数来做可能更合适点,不过我们还是用后缀数组来做一次,更熟悉后缀数组的作用。

做法其实很简单,枚举长度L,若 a^k = S, 那么 显然 Lcp{ rank[0], rank[L] } = n-L.  因为LCP总是与rank[0]比,可以预处理下,这样空间复杂度就降下来了,不过N= 1e6,  DA的NlogN的预处理会TLE,需要用 DC3的O(N)才能勉强 2700ms 过。  KMP 100Ms就能跑过去。。。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include#include#include

using namespacestd;const int N = int(1e6)+10;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa,intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}intsa[N],r[N], n, dp[N];charstr[N];voidInitRMQ(){int x = rank[0]; dp[x] =n;for(int i = x-1; i >= 1; i--)

dp[i]= min( dp[i+1], height[i+1] );for(int i = x+1; i <= n; i++)

dp[i]= min( dp[i-1], height[i] );

}

vectorS;intmain(){while( scanf("%s", str) !=EOF )

{if( strcmp(str,".") == 0 ) break;

n=strlen(str);for(int i = 0; i < n; i++)

r[i]=str[i];

r[n]= 0;

da(r,sa,n+1,128);

calheight(r,sa,n);

InitRMQ();

S.clear();for(int i = 1; i*i <= n; i++)

{if(n%i==0){

S.push_back(i);if( i*i != n ) S.push_back(n/i);

}

}

sort(S.begin(),S.end());for(vector::iterator it = S.begin(); it != S.end(); it++){int L = *it;if( n%L == 0){int t = dp[rank[L]]; //query( rank[0], rank[L] );

if( t == n-L ){

printf("%d\n", n/L );break;

}

}

}

}return 0;

}

DA

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include#include#include

using namespacestd;const int maxn = int(3e6)+10;const int N =maxn;#define F(x) ((x)/3+((x)%3==1?0:tb))

#define G(x) ((x)

intwa[maxn],wb[maxn],wv[maxn],ws[maxn];int c0(int *r,int a,intb)

{return r[a]==r[b]&&r[a+1]==r[b+1]&&r[a+2]==r[b+2];}int c12(int k,int *r,int a,intb)

{if(k==2) return r[a]

{inti;for(i=0;i=0;i--) b[--ws[wv[i]]]=a[i];return;

}void dc3(int *r,int *sa,int n,intm)

{int i,j,*rn=r+n,*san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;

r[n]=r[n+1]=0;for(i=0;i

sort(r+2,wa,wb,tbc,m);

sort(r+1,wb,wa,tbc,m);

sort(r,wa,wb,tbc,m);for(p=1,rn[F(wb[0])]=0,i=1;i

rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;if(p

sort(r,wb,wa,ta,m);for(i=0;i

sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];for(;i

}intheight[N], rank[N];void calheight(int *r,int *sa,intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}intsa[N],r[N], n, dp[N];charstr[N];voidInitRMQ(){int x = rank[0]; dp[x] =n;for(int i = x-1; i >= 1; i--)

dp[i]= min( dp[i+1], height[i+1] );for(int i = x+1; i <= n; i++)

dp[i]= min( dp[i-1], height[i] );

}

vectorS;intmain(){while( scanf("%s", str) !=EOF )

{if( strcmp(str,".") == 0 ) break;

n=strlen(str);for(int i = 0; i < n; i++)

r[i]=str[i];

r[n]= 0;

dc3(r,sa,n+1,128);

calheight(r,sa,n);

InitRMQ();

S.clear();for(int i = 1; i*i <= n; i++)

{if(n%i==0){

S.push_back(i);if( i*i != n ) S.push_back(n/i);

}

}

sort(S.begin(),S.end());for(vector::iterator it = S.begin(); it != S.end(); it++){int L = *it;if( n%L == 0){int t = dp[rank[L]]; //query( rank[0], rank[L] );

if( t == n-L ){

printf("%d\n", n/L );break;

}

}

}

}return 0;

}

DC3

poj 3693 连续重复次数最多的子串.

枚举构成 子串的单元长度L,  那么将原串用长度L等分为(0,L,2L,...最后段可能不全).

重复一次的我们特殊处理,显然是rank[1]所对应的单个字符。

我们只考虑 “单元串” 重复两次或以上构成的子串,那么我们可以知道,若存在长度为L的“单元串”,则必定有一对相邻的点 { L*i,L*(i+1) } 分别属于两个不同的“单元串”, 当以这两个为起点的后缀的LCP为M,则其将已经匹配长度为L的“单元串“ M/L+1次。  这里有个问题是,我们求得的 单元串重复多次构成不一定是最优或者起点,因为 M%L != 0的时候,可能不是起点, 这个时候  起点可能是  L*i - (L-M%L) , 抽象理解是补全循环,使其成为L的倍数,让它重复次数最大。

虽然这样,我们可以求出 最大重复次数 与其对应的长度。 但是题目需要求最小的字典序。 我们可以将最大重复次数与 合法的长度都存储起来(可能有多个合法长度,但重复次数唯一),然后我们可以 按排名从1-N分别求出对应长度的 最小字典序,然后取一个最小的排名即可。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include#include#include

using namespacestd;const int N = int(1e5)+10;int cmp(int *r,int a,int b,intl){return (r[a]==r[b]) && (r[a+l]==r[b+l]);

}//用于比较第一关键字与第二关键字,//比较特殊的地方是,预处理的时候,r[n]=0(小于前面出现过的字符)

intwa[N],wb[N],ws[N],wv[N];intrank[N],height[N];void da(int *r,int *sa,int n,int m){ //此处N比输入的N要多1,为人工添加的一个字符,用于避免CMP时越界

int i,j,p,*x=wa,*y=wb,*t;for(i=0;i=0;i--) sa[--ws[x[i]]]=i; //预处理长度为1

for(j=1,p=1;p

{for(p=0,i=n-j;i

for(i=0;i=j) y[p++]=sa[i]-j; //利用长度J的,按第二关键字排序

for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i]; //基数排序部分

for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i

x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; //更新名次数组x[],注意判定相同的

}

}void calheight(int *r,int *sa,int n){ //此处N为实际长度

int i,j,k=0; //height[]的合法范围为 1-N, 其中0是结尾加入的字符

for(i=1;i<=n;i++) rank[sa[i]]=i; //根据SA求RANK

for(i=0;i

for(k?k--:0,j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++); //根据 h[i] >= h[i-1]-1 来优化计算height过程

}intsa[N],r[N], n;charstr[N];int dp[N][20];voidInitRMQ(){for(int i = 1; i <= n; i++)

dp[i][0] =height[i];for(int j = 1; (1<

dp[i][j]= min( dp[i][j-1], dp[i+(1<

}int query(int L,intR){if(L>R) swap(L,R); L++;int k = (int)floor( log(1.*(R-L+1))/log(2.0) );return min( dp[L][k], dp[R-(1<

}intmain(){int Case = 1;while( scanf("%s",str) !=EOF)

{if(strcmp(str,"#")==0) break;

printf("Case %d:", Case++);

n=strlen(str);for(int i = 0; i < n; i++)

r[i]= str[i]-'a'+1;

r[n]= 0;

da(r,sa,n+1,27);

calheight(r,sa,n);

InitRMQ();int maxk = 0, maxl = 0;

vectorS; S.clear();for(int L = 1; L < n; L++)

{//printf("L = %d\n", L);

for(int i = 0; i+L < n; i+=L )

{int M = query( rank[i], rank[i+L] );int k = M/L+1, left =i;int t = L-M%L;

t= i-t;//printf("t = %d, M = %d, i = %d\n", t, M, i );

if( (t>=0) && (M%L) )

{int M1 = query( rank[t], rank[t+L] );int k1 = M1/L+1;if( k1 > k ) k =k1;

}//printf("i = %d, k = %d, L = %d\n", i, k, L);

if( k >maxk ){

maxk= k; maxl =L;

S.clear(); S.push_back(L);

}else if( (k==maxk) && (maxl

S.push_back(L), maxl=L;

}

}//debug//for(int i = 0; i < S.size(); i++)//printf("L = %d\n", S[i] );

if( (S.size()==0) || (maxk==1) ){

printf("%c\n", str[sa[1]] );continue;

}int st, len = -1;

vector< pair >res; res.clear();//printf("maxk = %d\n", maxk );

for(int j = 0; j < (int)S.size(); j++)

{int L =S[j];//printf("===> L = %d\n", L);

for(int i = 1; i <= n; i++)

{if( sa[i]+L

if( M >= L*(maxk-1) ){

res.push_back( make_pair(sa[i],L) );break;

}

}

}

}//printf("Debug:\n");//printf("LCP(3,5) = %d\n", query( rank[3], rank[5] ));//for(vector< pair >::iterator it=res.begin(); it != res.end(); it++ )//{//printf("st = %d, L = %d, rank = %d\n", it->first, it->second, rank[it->first]);//}

int Min = n+1;for(vector< pair >::iterator it=res.begin(); it != res.end(); it++)

{int a = it->first, b = it->second;if( rank[a] < Min ) st = a, len = b, Min =rank[a];

}

len*=maxk;//printf("st = %d\n", st );

for(int i = 0; i < len; i++)

printf("%c", str[st+i] );

puts("");

}return 0;

}

View Code

spoj 687 repeats  做法同上题样,只要求最大次数,代码简单了多。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include#include#include

using namespacestd;const int N = int(1e5)+10;int cmp(int *r,int a,int b,intl){return (r[a]==r[b]) && (r[a+l]==r[b+l]);

}//用于比较第一关键字与第二关键字,//比较特殊的地方是,预处理的时候,r[n]=0(小于前面出现过的字符)

intwa[N],wb[N],ws[N],wv[N];intrank[N],height[N];void da(int *r,int *sa,int n,int m){ //此处N比输入的N要多1,为人工添加的一个字符,用于避免CMP时越界

int i,j,p,*x=wa,*y=wb,*t;for(i=0;i=0;i--) sa[--ws[x[i]]]=i; //预处理长度为1

for(j=1,p=1;p

{for(p=0,i=n-j;i

for(i=0;i=j) y[p++]=sa[i]-j; //利用长度J的,按第二关键字排序

for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i]; //基数排序部分

for(t=x,x=y,y=t,p=1,x[sa[0]]=0,i=1;i

x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; //更新名次数组x[],注意判定相同的

}

}void calheight(int *r,int *sa,int n){ //此处N为实际长度

int i,j,k=0; //height[]的合法范围为 1-N, 其中0是结尾加入的字符

for(i=1;i<=n;i++) rank[sa[i]]=i; //根据SA求RANK

for(i=0;i

for(k?k--:0,j=sa[rank[i]-1]; r[i+k]==r[j+k]; k++); //根据 h[i] >= h[i-1]-1 来优化计算height过程

}intsa[N],r[N], n;charstr[N];int dp[N][20];voidInitRMQ(){for(int i = 1; i <= n; i++)

dp[i][0] =height[i];for(int j = 1; (1<

dp[i][j]= min( dp[i][j-1], dp[i+(1<

}int query(int L,intR){if(L>R) swap(L,R); L++;int k = (int)floor( log(1.*(R-L+1))/log(2.0) );return min( dp[L][k], dp[R-(1<

}intmain(){intT;

scanf("%d", &T);while(T--){

scanf("%d", &n);for(int i = 0; i < n; i++){

scanf("%s", str );

r[i]= str[0]-'a'+1;

}

r[n]= 0;

da(r,sa,n+1,27);

calheight(r,sa,n);

InitRMQ();int maxk = 0, maxl = 0;for(int L = 1; L < n; L++)

{//printf("L = %d\n", L);

for(int i = 0; i+L < n; i+=L )

{int M = query( rank[i], rank[i+L] );int k = M/L+1, left =i;int t = L-M%L;

t= i-t;//printf("t = %d, M = %d, i = %d\n", t, M, i );

if( (t>=0) && (M%L) )

{int M1 = query( rank[t], rank[t+L] );int k1 = M1/L+1;if( k1 > k ) k =k1;

}//printf("i = %d, k = %d, L = %d\n", i, k, L);

if( k > maxk ) maxk =k;

}

}

printf("%d\n", maxk);

}return 0;

}

View Code

两个字符串相关问题

两个串的最长公共子串长度

poj 3693  将两串拼接起来,中间连接出插入特殊字符, 然后问题就转换成了求最长的LCP,其满足两个后缀分属不同的串, 简单推理下,可以知道,根据传递性,两串的最长LCP必定相邻, 可以用反证法证明.

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include

using namespacestd;const int N = int(2e5)+10;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int *r,int *sa,int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa, intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}chars1[N], s2[N];intr[N], n1, n2, n;intsa[N];intmain(){while( scanf("%s%s",s1,s2) !=EOF)

{

n1= strlen(s1); n2 =strlen(s2);//{0,n1-1}, n1+{ 1,n2 }

for(int i = 0; i < n1; i++)

r[i]= (int)s1[i];

r[n1]= 1;for(int i = 0; i < n2; i++)

r[ n1+1+i ] = (int)s2[i];

r[n1+n2+1] = 0;

n= n1+n2;

da(r,sa,n+1,128);

calheight(r,sa,n);int Max = 0, st;for(int i = 2; i <= n; i++)

{if( height[i] >Max ){if( (sa[i-1]n1) || (sa[i-1]>n1&&sa[i]

Max= height[i], st = sa[i]>n1?sa[i-1]:sa[i];

}

}

printf("%d\n", Max);/*for(int i = 0; i < Max; i++)

printf("%c", s1[st+i]);

puts("");*/}return 0;

}

View Code

spoj 687 同上题一样,不过注意 N需要开到 3e5.否则会RE

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include

using namespacestd;const int N = int(3e5)+100;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int *r,int *sa,int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa, intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}chars1[N], s2[N];intr[N], n1, n2, n;intsa[N];intmain(){while( scanf("%d", &n) !=EOF)//while( scanf("%s%s",s1,s2) != EOF)

{

scanf("%s",s1); scanf("%s",s2);

n1= strlen(s1); n2 =strlen(s2);//{0,n1-1}, n1+{ 1,n2 }

for(int i = 0; i < n1; i++)

r[i]= (int)s1[i];

r[n1]= 1;for(int i = 0; i < n2; i++)

r[ n1+1+i ] = (int)s2[i];

r[n1+n2+1] = 0;

n= n1+n2;

da(r,sa,n+1,128);

calheight(r,sa,n);int Max = 0, st;for(int i = 2; i <= n; i++)

{if( height[i] >Max ){if( (sa[i-1]n1) || (sa[i-1]>n1&&sa[i]

Max= height[i], st = (sa[i]

}

}//printf("%d\n", Max);

for(int i = 0; i < Max; i++)

printf("%c", s1[st+i]);

puts("");

}return 0;

}

View Code

两个串的公共子串个数

poj 3415 求 长度不小于K的公共子串个数(可相同)。题目比较经典~~

分析过程:首先主观想法是,串A长度为n1,串B长度为n2, 总数量就是两个串的笛卡尔积。串A的n1个后缀与串B的n2个后缀满足要求的匹配数量。

对于10^5的N,O(N^2)显然不行,我们可以尝试着转换,是否计算过程能否转换成O(N)或者O(nlogn)的。

再仔细分析下,计算的过程,枚举串A所有后缀 st, 与B的所有后缀匹配。 我们提出一个猜想,当我们枚举串A的一个后缀suffix(i)与串B所有后缀计算完成后,再枚举 串A的后缀suffix(i+1) ,能否利用suffix(i)的计算信息来降低 扫描串B的所有后缀的 时间花费呢。 显然suffix(i) 与 suffix(i+1) 之间的关系对于公共子串而言 并不关键。但是LCP(最长公共前缀)就比较有用了。

关于排序后的后缀的性质这里就不说了。我们直接切入主题,相邻后缀不行,我们尝试使用相邻排名来处理。若我们将两个串连接起来,(连接点插入一个特殊字符)。

枚举串A的所有排名等价枚举所有A的后缀.当我们按照排名从小到大枚举 后缀x(可能是A的,也有可能是B的), 若假定此时是A的一个后缀, 那其前面的(排名比起小的)后缀中B的一个后缀y, 若与A的后缀x之间的 LCP( x,y ) >= k,  则此时有 ,若 y < x , 公共子串数量为y-k+1,  若 y > x, 公共子串数量为x-k+1,  显然与期间的最小值有关。

显然,若这样计算,我们只计算了 A的所有后缀x 与 排名比其小的B的后缀, 那些比X大的B的后缀还未被计算到。 下意识会想到倒着做一次,就可以了。 其实我们可以对B同A那样求一次,就等价反向再对A求一次了。

大概计算的思路就在这里了, 不过我们还需要解决如何利用前面的值来简化求解时间。

为了便于分析,我们假定只考虑 计算后缀排名在A之前的这一部分,因为另一部分是类似的。

首先我们将 height[i] = max( 0, height[i]-K+1 ),表示当前后缀贡献的子串数量。

维护一个 严格单调递增的 栈,在处理过程中,我们需要记录一个当前合法的串B的后缀贡献的子串数量,当加入进来一个A串时候,所有比起小的串B后缀都应该被纳入计算,比起大的部分我们应该减掉,  此时就需要记录那部分的数量。 中间过程不太好用文字描述。。。具体看代码吧。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include

using namespacestd;const int N = int(2e5)+10;

typedeflong longLL;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int *r,int *sa,int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa, intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}intn, n1, n2, K;intsa[N], r[N];chars1[N], s2[N];inth[N],na[N],nb[N],S[N];voidsolve(){for(int i = 2; i <= n; i++)

h[i]= max(0,height[i]-K+1);

LL ans= 0, w1 = 0, w2 = 0;int top = 0;for(int i = 2; i <= n; i++)

{

S[++top] =h[i];if( sa[i-1] <= n1 ) na[top]=1,nb[top]=0,w1 +=h[i];else na[top]=0, nb[top]=1, w2 +=h[i];while( (top>1) && (S[top]<=S[top-1]) ){

w1-= na[top-1]*(S[top-1]-S[top]);

w2-= nb[top-1]*(S[top-1]-S[top]);

na[top-1] += na[top]; nb[top-1] +=nb[top];

S[top-1] =S[top];

top--;

}if( sa[i] <= n1 ) ans +=w2;else ans +=w1;

}

printf("%lld\n", ans);

}intmain(){while( scanf("%d", &K), K)

{

scanf("%s%s",s1,s2);

n1= strlen(s1); n2 =strlen(s2);for(int i = 0; i < n1; i++)

r[i]= (int)s1[i];

r[n1]= 1;for(int i = 0; i < n2; i++)

r[n1+1+i] = (int)s2[i];

r[n=n1+n2+1] = 0;

da(r,sa,n+1,128);

calheight(r,sa,n);

solve();

}return 0;

}

View Code

多个字符串

通常解法是将其连接成一个串,然后跑DC3 or DA。

poj 3294  解法是二分枚举长度公共串长度L,将height值按>=L分组。然后随便搞就好了。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include#include

using namespacestd;const int N = int(3e5)+10;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int *r,int *sa,int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa, intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}char str[110][1010];int r[N], sa[N], n, len[110], a[110], cnt;boolvis[N];intK;

vectorS[N];bool check(intL){int cur = -1;for(int i = 1; i <= n; i++){if( height[i] < L ) S[++cur].clear();

S[cur].push_back(i);

}for(int i = 0; i <= cur; i++){if( S[i].size() >K ){

memset(vis,0,sizeof(vis));for(int j = 0; j < S[i].size(); j++)

{int k =S[i][j];int x = upper_bound(a,a+cnt+1, sa[ S[i][j] ] )-a-1;

vis[x]= true;

}int count = 0;for(int j = 0; j < cnt; j++)if(vis[j]) count++;if( count > K ) return true;

}

}return false;

}void gao(intL){int cur = -1;for(int i = 1; i <= n; i++){if( height[i] < L ) S[++cur].clear();

S[cur].push_back(i);

}for(int i = 0; i <= cur; i++){if( S[i].size() >K ){

memset(vis,0,sizeof(vis));for(int j = 0; j < S[i].size(); j++)

{int k =S[i][j];int x = upper_bound(a,a+cnt+1, sa[ S[i][j] ] )-a-1;

vis[x]= true;

}int count = 0;for(int j = 0; j < cnt; j++)if(vis[j]) count++;if( count >K )

{for(int j = 0; j < L; j++)

printf("%c", char(r[ sa[S[i][0]]+j ]) );

puts("");

}

}

}

}voidsolve(){

da(r,sa,n+1,310);

calheight(r,sa,n);

K= cnt/2;int l = 1, r = 1000, L = 0;while( l <=r ){int m = (l+r)>>1;if( check(m) ) L=m,l=m+1;else r = m-1;

}if( L == 0 ) puts("?");elsegao(L);

}intmain(){int Case = 0;while( scanf("%d",&cnt), cnt )

{if( Case++ > 0 ) puts("");for(int i = 0; i < cnt; i++)

{

scanf("%s",str[i]);

len[i]=strlen(str[i]);

}

n= 0;int tmp = 200;for(int i = 0; i < cnt; i++)

{if(i>0) r[n++] = tmp++;for(int j = 0; j < len[i]; j++)

r[n++] = (int)str[i][j];

}

r[n]= 0;

tmp= 0;for(int i = 0; i <= cnt; i++)

{

a[i]=tmp;if( i < cnt ) tmp = tmp + (i==0?len[i]:len[i]+1);

}

solve();

}return 0;

}

View Code

spoj 220 题目给出10个长度为10^4的串,让求在每个串中都至少出现两次的不重叠 子串最大长度。

做法同样是合并成一个串,然后二分子串长度L,因为不同串数量才10,暴力做判定就好了。关于不重叠和前面一样同一组Max-Min>=L即可。不过这里要注意是对于每一种串都必须满足才可。

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

#include#include#include#include#include

using namespacestd;const int N = int(3e5)+10;int cmp(int *r,int a,int b,intl)

{return r[a]==r[b] && r[a+l]==r[b+l];

}intwa[N],wb[N],ws[N],wv[N];intheight[N], rank[N];void da(int *r,int *sa,int n,intm){int i,j,p,*x=wa,*y=wb;for(i=0;i=0;i--) sa[--ws[x[i]]] =i;for(j=1,p=1;p

{for(p=0,i=n-j;i=j) y[p++]=sa[i]-j;for(i=0;i=0;i--) sa[--ws[wv[i]]]=y[i];for(swap(x,y),p=1,x[sa[0]]=0,i=1;i

x[sa[i]]= cmp(y,sa[i-1],sa[i],j)?p-1:p++;

}

}void calheight(int *r,int *sa, intn){int i,j,k=0;for(i=1;i<=n;i++) rank[sa[i]]=i;for(i=0;i

}char str[15][10100];int len[15], a[N], n, M;intsa[N], r[N];

vectorS[N];int Min[15], Max[15];bool check(intL){int cur = -1;for(int i = 1; i <= n; i++){if( height[i] < L ) S[++cur].clear();

S[cur].push_back(i);

}for(int i = 0; i <= cur; i++)

{if( S[i].size() >= 2*M ){

memset(Min,-1,sizeof(Min));

memset(Max,-1,sizeof(Max));for(int j = 0; j < S[i].size(); j++)

{int v = S[i][j]; //rank

int idx = upper_bound(a,a+M+1,sa[v])-a-1;

Min[idx]= Min[idx]==-1?sa[v]: min(Min[idx],sa[v]);

Max[idx]= Max[idx]==-1?sa[v]: max(Max[idx],sa[v]);

}bool flag = true;for(int i = 0; i < M; i++){if( (Min[i]==-1) || (Max[i]-Min[i]

{

flag= false; break;

}

}if(flag) return true;

}

}return false;

}voidsolve(){

da(r,sa,n+1,128);

calheight(r,sa,n);int l = 1, r = N, ans = 0;while( l <=r ){int m = (l+r)>>1; //printf("m = %d\n", m);

if( check(m) ) ans=m, l = m+1;else r = m-1;

}

printf("%d\n", ans );

}intmain(){int_;

scanf("%d", &_);while( _--)

{

scanf("%d", &M);for(int i = 0; i < M; i++)

{

scanf("%s", str[i]);

len[i]=strlen(str[i]);

}

n= 0; int tmp = 0;for(int i = 0; i < M; i++)

{if(i > 0) r[n++] = ++tmp;for(int j = 0; j < len[i]; j++)

r[n++] = int(str[i][j]);

}

r[n]= 0; tmp = 0;for(int i = 0; i <= M; i++){

a[i]=tmp;if(i < M) tmp += (i==0)?len[i]:len[i]+1;

}

solve();

}return 0;

}

View Code

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值