最近看的后缀数组,做了几个应用。
1、poj 2774 求两个字符串的最长公共子串。
思路:将第二个串接到第一个串的后面,第一个串的结尾和第二个串的结尾分别用一个不会出现的字符标记,第二个串的结尾标记值要小。
用大牛的模板求出sa,height,rank数组。然后去遍历数组求前面的和后面的最大公共前缀。判定的时候注意判定条件,我在判定的时候判定
条件有漏洞,然后wa了好多次~~
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
#define mid(x,y) (x+y)>>1
#define INF (INT_MAX/10)
#define SQR(x) x*x
#define rep(i, n) for(int i=0;i<n;i++)
#define repf(i, a, b) for(int i=a;i<=b;i++)
#define repd(i, a, b) for(int i=a;i>=b;i--)
#define clr(ar, val) memset(ar, val, sizeof(ar))
#define N 1000000
int sa[N+10],c[N+10],wa[N+10],wb[N+10];
int cmp(int *y,int a,int b,int l){
return y[a]==y[b]&&y[a+l]==y[b+l];
}
void da(string r,int *sa){
int i,j,p,*rand=wa,*y=wb,m=255,n=r.length();
for(i=0;i<=m;i++) c[i]=0;
for(i=0;i<n;i++) c[rand[i]=r[i]]++;
for(i=0;i<m;i++) c[i+1]+=c[i];
for(i=n-1;i>=0;i--) sa[--c[rand[i]]]=i;
for(j=1,p=1;p<n;j*=2,m=p){
for(i=n-j,p=0;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++) c[i]=0;
for(i=0;i<n;i++) c[rand[y[i]]]++;
for(i=0;i<m;i++) c[i+1]+=c[i];
for(i=n-1;i>=0;i--) sa[--c[rand[y[i]]]]=y[i];
swap(y,rand);
for(i=1,p=1,rand[sa[0]]=0;i<n;i++){
rand[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
}
}
}
void height(string r,int *sa,int *h){
int i,j,*rank=wa,k=0,n=r.length();
for(i=0;i<n;i++) rank[sa[i]]=i;
for(i=0;i<n-1;h[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
string str1,str2;
int ans[N+10],h[N+10];
int main(){
int k,maxs;
while(cin>>str1>>str2){
clr(ans,0);clr(h,0);
str1=str1+'$'+str2+'#';
//cout << str1 << endl;
k=str1.find('$');
//cout << k << ' ' << str1.length() << endl;
da(str1,ans);
//rep(i,10) cout << ans[i] << ' ' ;
//cout << endl;
height(str1,ans,h);
//rep(i,10) cout << h[i] << ' ' ;
//cout << endl;
maxs=0;
repf(i,1,(int)str1.length()-1)
if((ans[i]-k)*(ans[i-1]-k+h[i])<0||(ans[i]-k+h[i])*(ans[i-1]-k)<0)
maxs=max(maxs,h[i]);
cout << maxs << endl;
}
return 0;
}
2、poj 1743 求一个字符串中重复出现的最长的子串(不可重叠)
思路:数据要先预处理成一组新的字符串,即后面的减掉前面的。对新串求sa,height,rank数组,然后二分的去搜可能的值,我当时没有明白二分的意义,
后来看了别人的代码,再对着文字的理论结合大概理解了。找一个可能的长度,带到字符串中,判定是否可行,可行的话看是否有更长的,不可行看是不
是有更短的。这样二分的找出结果。
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
#define Mid(x,y) (x+y)>>1
#define INF (INT_MAX/10)
#define SQR(x) x*x
#define rep(i, n) for(int i=0;i<n;i++)
#define repf(i, a, b) for(int i=a;i<=b;i++)
#define repd(i, a, b) for(int i=a;i>=b;i--)
#define clr(ar, val) memset(ar, val, sizeof(ar))
#define N 1000000
int sa[N+10],c[N+10],wa[N+10],wb[N+10];
bool cmp(int *y,int a,int b,int l){
return y[a]==y[b]&&y[a+l]==y[b+l];
}
template <class T>
void suffix(T r,int n){
int i,j,p,*rank=wa,*y=wb,m=255;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[rank[i]=r[i]]++;
for(i=0;i<m;i++) c[i+1]+=c[i];
for(i=n-1;i>=0;i--) sa[--c[rank[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<m;i++) c[i]=0;
for(i=0;i<n;i++) c[rank[y[i]]]++;
for(i=0;i<m;i++) c[i+1]+=c[i];
for(i=n-1;i>=0;i--) sa[--c[rank[y[i]]]]=y[i];
swap(rank,y);
for(i=1,p=1,rank[sa[0]]=0;i<n;i++)
rank[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
}
}
int h[N+10];
template <class T>
void height(T r,int n){
int i,j,k=0,*rank=wa;
for(i=0;i<n;i++) rank[sa[i]]=i;
for(i=0;i<n-1;h[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
int check(int mid,int len){
for(int i=2;i<len;i++){
if(h[i]<mid) continue;
for(int j=i-1;j>=2;j--){
if(abs(sa[i]-sa[j])>mid) return 1;
if(h[j]<mid) break;
}
}
return 0;
}
int str[N+10];
int main(){
int n,ans;
while(1){
cin >> n;
if(!n) break;
rep(i,n) {
scanf("%d",str+i);
if(i) str[i-1]=str[i]-str[i-1]+100;
}
if(n<10){
cout << 0 << endl;
continue;
}
str[n-1]=0;
// rep(i,n) cout << str[i] << ' ' ;
// cout << endl;
suffix(str,n);
height(str,n);
// rep(i, n) cout << sa[i] << ' ' ;
// cout << endl;
// rep(i, n) cout << h[i] << ' ' ;
// cout << endl;
int mid,left,right;
left=3,right=n;
ans=0;
while(right>=left){
mid=Mid(left,right);
// cout << mid << endl;
if(check(mid,n)){
left=mid+1;
ans=mid;
}
else right=mid-1;
}
// cout << ans << endl;
if(ans<4)
cout << 0 << endl;
else
cout << ans+1 << endl;
}
return 0;
}
3、poj 3261 同样的求最长的重复子串
题意很明确,求一个最长的出现K 次的重复子串,可以重叠。同样处理出sa,height,rank数组,再二分的搜索可能的长度。这个题我wa了几次,
wa的有点坑,一个等号的问题(<=len)!!!!eggache~~
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <cmath>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
using namespace std;
typedef long long ll;
#define Mid(x,y) (x+y)>>1
#define INF (INT_MAX/10)
#define SQR(x) x*x
#define rep(i, n) for(int i=0;i<n;i++)
#define repf(i, a, b) for(int i=a;i<=b;i++)
#define repd(i, a, b) for(int i=a;i>=b;i--)
#define clr(ar, val) memset(ar, val, sizeof(ar))
#define N 1000000
int sa[N+10],c[N+10],wa[N+10],wb[N+10];
bool cmp(int *y,int a,int b,int l){
return y[a]==y[b]&&y[a+l]==y[b+l];
}
template <class T>
void suffix(T r,int n,int m){
int i,j,p,*rank=wa,*y=wb;
for(i=0;i<m;i++) c[i]=0;
for(i=0;i<n;i++) c[rank[i]=r[i]]++;
for(i=0;i<m;i++) c[i+1]+=c[i];
for(i=n-1;i>=0;i--) sa[--c[rank[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<m;i++) c[i]=0;
for(i=0;i<n;i++) c[rank[y[i]]]++;
for(i=0;i<m;i++) c[i+1]+=c[i];
for(i=n-1;i>=0;i--) sa[--c[rank[y[i]]]]=y[i];
swap(rank,y);
for(i=1,p=1,rank[sa[0]]=0;i<n;i++)
rank[sa[i]]=cmp(y,sa[i],sa[i-1],j)?p-1:p++;
}
}
int h[N+10];
template <class T>
void height(T r,int n){
int i,j,k=0,*rank=wa;
for(i=0;i<n;i++) rank[sa[i]]=i;
for(i=0;i<n-1;h[rank[i++]]=k)
for(k?k--:0,j=sa[rank[i]-1];r[i+k]==r[j+k];k++);
}
int check(int mid,int len,int k){
int i,j=1;
for(i=2;i<=len;i+=j){
// cout << mid << ' ' << k << endl;
if(h[i]<mid) continue;
int flag=0;
while(i<=len&&h[i]>=mid)
flag++,i++;
if(flag>=k) return 1;
}
return 0;
}
int str[N+10];
int main(){
int n,k;
while(cin>>n>>k){
rep(i,n) {
scanf("%d",str+i);
str[i]++;
}
str[n]=0;
suffix(str,n+1,N+3);
height(str,n+1);
// rep(i, n) cout << h[i] << ' ' ;
// cout << endl;
int ans=0,mid,left,right;
left=0,right=n;//=*max_element(h+2,h+n);
// cout << "fuck" << right << endl;
while(right>=left){
mid=Mid(left,right);
// cout << "fuck mid " << mid << endl;
if(check(mid,n,k-1))
left=mid+1,ans=mid;
else right=mid-1;
}
cout << ans << endl;
}
return 0;
}