「kuangbin帶你飛」專題十八 後綴數組
layout: post
title: 「kuangbin帶你飛」專題十八 後綴數組
author: "luowentaoaa"
catalog: true
tags:
- kuangbin
- 字符串
- 後綴數組
傳送門
倍增法
struct DA{ bool cmp(int *r,int a,int b,int l){ return r[a]==r[b]&&r[a+l]==r[b+l]; } int t1[maxn],t2[maxn],c[maxn]; int rank[maxn],height[maxn],RMQ[maxn],mm[maxn]; int best[20][maxn]; int r[maxn]; int sa[maxn]; int str[maxn]; void da(int n,int m){ n++; int i,j,p,*x=t1,*y=t2; for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[i]=str[i]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i; for(j=1;j<=n;j<<=1){ p=0; for(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[x[y[i]]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i]; swap(x,y); p=1;x[sa[0]]=0; for(i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; if(p>=n)break; m=p; } int k=0; n--; for(i=0;i<=n;i++)rank[sa[i]]=i; for(i=0;i<n;i++){ if(k)k--; j=sa[rank[i]-1]; while(str[i+k]==str[j+k])k++; height[rank[i]]=k; } } void initRMQ(int n){ for(int i=1;i<=n;i++)RMQ[i]=height[i]; mm[0]=-1; for(int i=1;i<=n;i++) mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1]; for(int i=1;i<=n;i++)best[0][i]=i; for(int i=1;i<=mm[n];i++) for(int j=1;j+(1<<i)-1<=n;j++){ int a=best[i-1][j]; int b=best[i-1][j+(1<<(i-1))]; if(RMQ[a]<RMQ[b])best[i][j]=a; else best[i][j]=b; } } int askRMQ(int a,int b){ int t; t=mm[b-a+1]; b-=(1<<t)-1; a=best[t][a];b=best[t][b]; return RMQ[a]<RMQ[b]?a:b; } int lcp(int a,int b){ a=rank[a];b=rank[b]; if(a>b)swap(a,b); return height[askRMQ(a+1,b)]; } void print(int n){ cout<<"sa[] "; for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl; cout<<"rank[] "; for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl; cout<<"height[] "; for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl; } }AA,BB;
A.POJ1743 Musical Theme
題意
有N(1<=N<=20000)個音符的序列來表示一首樂曲,每個音符都是1..88範圍內的整數,現在要找一個重復的子串,它需要滿足如下條件:1.長度至少為5個音符。
2.在樂曲中重復出現(就是出現過至少兩次)。(可能經過轉調,“轉調”的意思是主題序列中每個音符都被加上或減去了同一個整數值)
3.重復出現的同一主題不能有公共部分。
思路
對於第一二點:至少長度是五,我們可以二分0-n/2之間的長度來查找height(height[i]=排名為i和i-1的後綴子串的最長公共前綴長度綴)然後找到兩個大於二分的值,然後判斷起點距離是否大於二分的值(因為要求不重疊)
第三點 因為共同加上一個值,但是他們前後的差值還是一樣的(例如1,2,3和5,6,7是一樣的他們的差值是1)然後把原數組變成一個長度為N-1的數組去處理,因為處理後的數組比原數組少1所以答案也要-1;
//#include<bits/stdc++.h> #include<algorithm> #include<iostream> using namespace std; typedef long long ll; const ll mod=998244353; const int maxn=2e5; const ll inf=0x3f3f3f3f3f3f; #define bug cout<<"here:"<<endl; int sa[maxn]; //sa[i]名次為i的後綴的起始位置; //Rank[i]起始位置為i的名次 ->Rank[sa[i]]=i; //height[i]名次為i和i-1的後綴的最長公共前綴長; int t1[maxn],t2[maxn],c[maxn]; int Rank[maxn],height[maxn]; bool cmp(int *r,int a,int b,int l){ return r[a]==r[b]&&r[a+l]==r[b+l]; } int s[maxn]; void da(int str[],int n,int m){ int i,j,p,*x=t1,*y=t2; for(i=0;i<m;i++)c[i]=0; for(i=0;i<n;i++)c[x[i]=str[i]]++;//*** for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i; for(j=1;j<=n;j<<=1){ p=0; for(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[x[y[i]]]++; for(i=1;i<m;i++)c[i]+=c[i-1]; for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i]; swap(x,y); p=1;x[sa[0]]=0; for(i=1;i<n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++; if(p>=n)break; m=p; } int k=0; n--; for(i=0;i<=n;i++)Rank[sa[i]]=i; for(i=0;i<n;i++){ if(k)k--; j=sa[Rank[i]-1]; while(s[i+k]==s[j+k])k++; height[Rank[i]]=k; } } bool isok(int n,int k){ int ma=sa[1],mi=sa[1]; for(int i=2;i<=n;i++){ if(height[i]<k)ma=mi=sa[i]; else{ if(sa[i]<mi)mi=sa[i]; if(sa[i]>ma)ma=sa[i]; if(ma-mi>k)return true; } }return false; } int main() { std::ios::sync_with_stdio(false); std::cin.tie(0); std::cout.tie(0); int n; while(cin>>n&&n){ for(int i=0;i<n;i++)cin>>s[i]; for(int i=n-1;i>0;i--)s[i]=s[i]-s[i-1]+90;//因為四十行要用s[i]的值做下表所以不能出現負數 n--;//變化後的長度-1 for(int i=0;i<n;i++)s[i]=s[i+1]; s[n]=0; da(s,n+1,200); int ans=-1; int l=1,r=n/2; while(l<=r){ int mid=(l+r)/2; if(isok(n,mid)){ ans=mid; l=mid+1; } else r=mid-1; } if(ans<4)cout<<0<<endl; else cout<<ans+1<<endl; } return 0; }
附加1. #1403 : 後綴數組一·重復旋律
題意
最長可重疊重復K次子串問題。
題解
二分答案
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
#define rank rankk
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int t1[maxn],t2[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(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[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
int rank[maxn];//後綴i在sa[]中的排名
int height[maxn];//sa[i]與sa[i-1]的LCP(最長公共前綴)
int str[maxn];
int r[maxn];
int sa[maxn];//sa[i]表示排名弟i小的後綴的下標
int n,m;
int solve(int k){
int tmp=1;
for(int i=1;i<n;){
int cnt=1;
int j=i+1;
while(height[j]>=k&&j<=n)j++,cnt++;
tmp=max(cnt,tmp);
i=j;
}
return tmp>=m;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++)cin>>str[i];
str[n]=0;//多補一個0
da(str,sa,rank,height,n,128);
int l=0,r=n,ans=-1;
while(l<=r){
int mid=(l+r)>>1;
if(solve(mid))ans=mid,l=mid+1;
else r=mid-1;
}
cout<<ans<<endl;
return 0;
}
附加2. #1407 : 後綴數組二·重復旋律2
題意
最長不可重疊重復子串問題
題解
只要判斷長度是否大於二分的就行
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
#define rank rankk
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int t1[maxn],t2[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(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[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
int rank[maxn];//後綴i在sa[]中的排名
int height[maxn];//sa[i]與sa[i-1]的LCP(最長公共前綴)
int str[maxn];
int r[maxn];
int sa[maxn];//sa[i]表示排名弟i小的後綴的下標
int n,m;
int solve(int k){
int minsa,maxsa;
for(int i=1;i<=n;i++)
if(height[i]<k){
minsa=sa[i];
maxsa=sa[i];
}
else{
minsa=min(minsa,sa[i]);
maxsa=max(maxsa,sa[i]);
if(maxsa-minsa>=k)return true;
}
return false;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>n;
for(int i=0;i<n;i++)cin>>str[i];
str[n]=0;//多補一個0
da(str,sa,rank,height,n,1280);
int l=0,r=n,ans=-1;
while(l<=r){
int mid=(l+r)>>1;
if(solve(mid))ans=mid,l=mid+1;
else r=mid-1;
}
cout<<ans<<endl;
return 0;
}
附加3.#1415 : 後綴數組三·重復旋律3
題意
經典的最長公共子串問題//
如果同一段旋律在作品A和作品B中同時出現過,這段旋律就是A和B共同的部分,比如在abab 在 bababab 和 cabacababc 中都出現過。小Hi想知道兩部作品的共同旋律最長是多少?
題解
把兩個字符串接在一起就變成了求後綴的最長公共前綴的問題,但是由於這個前綴不能跨越兩個字符串,所以我們在第一個字符串後面加上一個沒有出現過的字符,再接第二個字符串,然後取所有非同一個串後綴的height的最大值...
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
#define rank rankk
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int t1[maxn],t2[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(char str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(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[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
int rank[maxn];//後綴i在sa[]中的排名
int height[maxn];//sa[i]與sa[i-1]的LCP(最長公共前綴)
char str[maxn];
char str2[maxn];
int r[maxn];
int sa[maxn];//sa[i]表示排名弟i小的後綴的下標
int n,m;
int solve(int n,int len1){
int ans=0;
for(int i=1;i<=n;i++){
if((sa[i-1]<=len1&&sa[i]>len1)||(sa[i-1]>len1&&sa[i]<=len1))
ans=max(ans,height[i]);
}
return ans;
}
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>str;
int len1=strlen(str);
str[len1]='#';
cin>>str2;
int len2=strlen(str2);
for(int i=0;i<len2;i++){
str[len1+1+i]=str2[i];
}
str[len1+1+len2]=0;
n=len1+len2+1;
da(str,sa,rank,height,n,128);
cout<<solve(n,len1)<<endl;
return 0;
}
附加4.#1419 : 後綴數組四·重復旋律4
題意
重復次數最多的連續字串
小Hi平時的一大興趣愛好就是演奏鋼琴。我們知道一個音樂旋律被表示為長度為 N 的數構成的數列。小Hi在練習過很多曲子以後發現很多作品中的旋律有重復的部分。
我們把一段旋律稱為(k,l)-重復的,如果它滿足由一個長度為l的字符串重復了k次組成。 如旋律abaabaabaaba是(4,3)重復的,因為它由aba重復4次組成。
小Hi想知道一部作品中k最大的(k,l)-重復旋律。
題解
求重復次數最多的連續字串
假如知道了這個連續子串的開始位置
並且知道了它的長度
那麽,此時我們就通過lcp(i,i+len)來進行比較即可
但是,如果枚舉長度和開始的位置的話
盡管lcp通過st表可以O(1)查詢
但是這個枚舉的復雜度是O(n2)的
很顯然,需要更快
所以,枚舉了長度lenlen之後
我們只需要考慮開始位置為lenlen倍數的地方
如果此時有一個重復串的開始位置不在lenlen的倍數上
很顯然的
lcp就會多出一截
所以,我們可以倒推出這個位置
所以,這樣枚舉復雜度為O(nlogn)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
#define rank rankk
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
int t1[maxn],t2[maxn],c[maxn];
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
void da(char str[],int sa[],int rank[],int height[],int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(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[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
int rank[maxn];//後綴i在sa[]中的排名
int height[maxn];//sa[i]與sa[i-1]的LCP(最長公共前綴)
int RMQ[maxn];
int mm[maxn];
int best[20][maxn];
void initRMQ(int n){
mm[0]=-1;
for(int i=1;i<=n;i++)
mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
for(int i=1;i<=n;i++)best[0][i]=i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++){
int a=best[i-1][j];
int b=best[i-1][j+(1<<(i-1))];
if(RMQ[a]<RMQ[b])best[i][j]=a;
else best[i][j]=b;
}
}
int askRMQ(int a,int b){
int t;
t=mm[b-a+1];
b-=(1<<t)-1;
a=best[t][a];b=best[t][b];
return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b){//求以a,b開始的字串的最長公共前綴
a=rank[a];b=rank[b];
if(a>b)swap(a,b);
return height[askRMQ(a+1,b)];
}
char str[maxn];
int r[maxn];
int sa[maxn];//sa[i]表示排名弟i小的後綴的下標
int n,m;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>str;
n=strlen(str);
str[n]=0;
da(str,sa,rank,height,n,128);
for(int i=1;i<=n;i++)RMQ[i]=height[i];
initRMQ(n);
int ans=1;
for(int i=1;i<=n;i++){
for(int j=0;j+i<n;j+=i){
int len=lcp(j,j+i);
int k=j-(i-len%i);
int sum=len/i+1;
if(k>=0&&lcp(k,k+i)>=i){
sum++;
}
ans=max(ans,sum);
}
}
cout<<ans<<endl;
return 0;
}
B.UVA - 10829 Gap Substrings
題意
題目即求有多少子串滿足ABA的形式,並且滿足|A|>0,|B|=G。
題解
搞了我三天三夜的題目
首先可以用後綴數組求出任意兩個點的後綴的lcp,然後相同長度減去g就是答案
但是這樣復雜度是n^2
然後我們可以發現假設A的長度是L,第一個L在K位置,那麽另一個L是K+L+G
然後我們發現如果L和另一個K+L+G判斷相同了之後,那麽L+1,L+2,...2L-1其實都不用再匹配了
所以我們就直接枚舉長度L 然後在0,L,2L,3L進行匹配,註意不僅僅要向後面匹配前綴,還要向前面匹配後綴,後者我們可以把字符串倒置,然後求n-1-k,和n-1-(k+l+g)的lcp 這樣兩個lcp相加,那A的起點就可以在他們這個範圍裏面 所以答案就是lcp1+lcp2-L
註意lcp長度不能超過L,因為超過的話就會訪問到另一個L的區域了
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct DA{
bool cmp(int *r,int a,int b,int l){
return r[a]==r[b]&&r[a+l]==r[b+l];
}
int t1[maxn],t2[maxn],c[maxn];
int rank[maxn],height[maxn],RMQ[maxn],mm[maxn];
int best[20][maxn];
int r[maxn];
int sa[maxn];
int str[maxn];
void da(int n,int m){
n++;
int i,j,p,*x=t1,*y=t2;
for(i=0;i<m;i++)c[i]=0;
for(i=0;i<n;i++)c[x[i]=str[i]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
for(j=1;j<=n;j<<=1){
p=0;
for(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[x[y[i]]]++;
for(i=1;i<m;i++)c[i]+=c[i-1];
for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
swap(x,y);
p=1;x[sa[0]]=0;
for(i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p>=n)break;
m=p;
}
int k=0;
n--;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void initRMQ(int n){
for(int i=1;i<=n;i++)RMQ[i]=height[i];
mm[0]=-1;
for(int i=1;i<=n;i++)
mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
for(int i=1;i<=n;i++)best[0][i]=i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++){
int a=best[i-1][j];
int b=best[i-1][j+(1<<(i-1))];
if(RMQ[a]<RMQ[b])best[i][j]=a;
else best[i][j]=b;
}
}
int askRMQ(int a,int b){
int t;
t=mm[b-a+1];
b-=(1<<t)-1;
a=best[t][a];b=best[t][b];
return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b){
a=rank[a];b=rank[b];
if(a>b)swap(a,b);
return height[askRMQ(a+1,b)];
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}AA,BB;
char s[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
/*int len=strlen(s);
for(int i=0;i<len;i++)AA.str[i]=s[i];
AA.da(len,120);
AA.print(len);*/
int t;
cin>>t;
int CC=1;
while(t--){
int g;
cin>>g>>s;
int len=strlen(s);
//cout<<"s=="<<s<<endl;
//cout<<"len="<<len<<endl;
for(int i=0;i<len;i++){
AA.str[i]=s[i]-'a'+1;
BB.str[i]=s[len-i-1]-'a'+1;
}
AA.da(len,30);BB.da(len,30);
AA.initRMQ(len);BB.initRMQ(len);
//AA.print(len);
// BB.print(len);
ll ans=0;
for(int i=1;i<=len;i++){
for(int j=0;j+i+g<len;j+=i){
int l=j,r=j+i+g;
int lll=min(i,AA.lcp(l,r));
int rrr=min(i,BB.lcp(len-l-1,len-r-1));
int len=(lll+rrr);
if(len>=i)ans+=ll(1LL*len-i);
}
}
cout<<"Case "<<CC++<<": ";
cout<<ans<<endl;
}
return 0;
}
/*
2
1 bbaabaaaaa
5 abxxxxxab
*/
dc3
struct DA{
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int sa[maxn*20],rank[maxn*20],height[maxn*20],str[maxn*20];
int wa[maxn*20],wb[maxn*20],wv[maxn*20],wss[maxn*20];
int c0(int *r,int a,int b){
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,int b){
if(k==2)
return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));
else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);
}
void sort(int *r,int *a,int *b,int n,int m){
int i;
for(i=0;i<n;i++)wv[i]=r[a[i]];
for(i=0;i<m;i++)wss[i]=0;
for(i=0;i<n;i++)wss[wv[i]]++;
for(i=1;i<m;i++)wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
b[--wss[wv[i]]]=a[i];
}
void dc3(int *r,int *sa,int n,int m){
int i,j,*rn=r+n;
int *san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0;i<n;i++)if(i%3!=0)wa[tbc++]=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<tbc;i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc)dc3(rn,san,tbc,p);
else for(i=0;i<tbc;i++)san[rn[i]]=i;
for(i=0;i<tbc;i++)if(san[i]<tb)wb[ta++]=san[i]*3;
if(n%3==1)wb[ta++]=n-1;
sort(r,wb,wa,ta,m);
for(i=0;i<tbc;i++)wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0;i<ta&&j<tbc;p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(;i<ta;p++)sa[p]=wa[i++];
for(;j<tbc;p++)sa[p]=wb[j++];
}
void da(int n,int m){
for(int i=n;i<n*3;i++)str[i]=0;
dc3(str,sa,n+1,m);
int i,j,k=0;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}DA;
A.Mediocre String Problem (2018南京M,回文+LCP)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=1e6+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct DA{
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int sa[maxn*20],rank[maxn*20],height[maxn*20],str[maxn*20];
int wa[maxn*20],wb[maxn*20],wv[maxn*20],wss[maxn*20];
int c0(int *r,int a,int b){
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,int b){
if(k==2)
return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));
else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);
}
void sort(int *r,int *a,int *b,int n,int m){
int i;
for(i=0;i<n;i++)wv[i]=r[a[i]];
for(i=0;i<m;i++)wss[i]=0;
for(i=0;i<n;i++)wss[wv[i]]++;
for(i=1;i<m;i++)wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
b[--wss[wv[i]]]=a[i];
}
void dc3(int *r,int *sa,int n,int m){
int i,j,*rn=r+n;
int *san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0;i<n;i++)if(i%3!=0)wa[tbc++]=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<tbc;i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc)dc3(rn,san,tbc,p);
else for(i=0;i<tbc;i++)san[rn[i]]=i;
for(i=0;i<tbc;i++)if(san[i]<tb)wb[ta++]=san[i]*3;
if(n%3==1)wb[ta++]=n-1;
sort(r,wb,wa,ta,m);
for(i=0;i<tbc;i++)wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0;i<ta&&j<tbc;p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(;i<ta;p++)sa[p]=wa[i++];
for(;j<tbc;p++)sa[p]=wb[j++];
}
void da(int n,int m){
for(int i=n;i<n*3;i++)str[i]=0;
dc3(str,sa,n+1,m);
int i,j,k=0;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}DA;
struct PalTree{
int next[maxn][26],fail[maxn],cnt[maxn],num[maxn],len[maxn],S[maxn],last,n,p;
int newnode(int l){
for(int i=0;i<26;i++)next[p][i]=0;
cnt[p]=num[p]=0;len[p]=l;return p++;
}
void init(){
p=0;newnode(0);newnode(-1);last=0;n=0;S[n]=-1;fail[0]=1;
}
int get_fail(int x){
while(S[n-len[x]-1]!=S[n])x=fail[x];return x;
}
int add(int c){
c-='a';S[++n]=c;int cur=get_fail(last);
if(!next[cur][c]){
int now=newnode(len[cur]+2);
fail[now]=next[get_fail(fail[cur])][c];
next[cur][c]=now;num[now]=num[fail[now]]+1;
}
last=next[cur][c];cnt[last]++;return num[last];
}
void count(){for(int i=p-1;i>=0;i--)cnt[fail[i]]+=cnt[i];}
}PAM;
char s[maxn],t[maxn];
int num[maxn],cnt[maxn];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
cin>>s>>t;
int lens=strlen(s),lent=strlen(t);
int len=0;PAM.init();
for(int i=0;i<lens;i++)DA.str[len++]=s[lens-i-1]-'a'+1,cnt[lens-i-1]=PAM.add(s[lens-i-1]);
DA.str[len++]=30;
for(int i=0;i<lent;i++)DA.str[len++]=t[i]-'a'+1;
DA.str[len]=0;
DA.da(len,200);
int p=DA.rank[lens+1];
//DA.print(len);
int now=len+1;
for(int i=p-1;i>=0;i--){
now=min(now,DA.height[i+1]);
if(DA.sa[i]>=0&&DA.sa[i]<lens){
num[lens-1-DA.sa[i]]=now;
}
}
now=len+1;
for(int i=p+1;i<=len;i++){
now=min(now,DA.height[i]);
if(DA.sa[i]>=0&&DA.sa[i]<lens){
num[lens-1-DA.sa[i]]=now;
}
}
ll ans=0;
for(int i=0;i<lens-1;i++){
ans+=1LL*num[i]*cnt[i+1];
}
cout<<ans<<endl;
return 0;
}
I.POJ - 3415 Common Substrings
單調棧優化
題意
求長度不小於K的公共子串的個數。
題解
對於一個LCP 貢獻是len(lcp)-k+1
我們可以將height進行分組,大於等於k的在同一組,如果兩個後綴的最長公共子串>=k,那麽它們肯定在同一個組內。現在從頭開始掃,每遇到A的後綴時,就統計一下它和它前面的B的後綴能組成多少長度>=k的公共子串,然後再反過來處理B的後綴即可,一共需要掃兩遍。但是這樣的時間復雜度是O(n^2),是行不通的。
因為兩個後綴的LCP是它們之間的最小height值,所以可以維護一個自底向上遞增的單調棧,如果有height值小於了當前棧頂的height值,那麽大於它的那些只能按照當前這個小的值來計算。這樣分別處理兩次,一次處理A的後綴,一次處理B的後綴。需要記錄好棧裏的和以及每個組內的後綴數。
註意longlong
#include<string>
#include<iostream>
#include<iosfwd>
#include<cmath>
#include<cstring>
#include<stdlib.h>
#include<stdio.h>
#include<cstring>
using namespace std;
typedef long long ll;
#define pp pair<int,int>
const ll mod=998244353;
const int maxn=2e5+50;
const ll inf=0x3f3f3f3f3f3f3f3fLL;
int gcd(int a,int b){while(b){int t=a%b;a=b;b=t;}return a;}
int lcm(int a,int b){return a*b/gcd(a,b);}
struct DA{
#define F(x) ((x)/3+((x)%3==1?0:tb))
#define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
int sa[maxn*10],rank[maxn*10],height[maxn*10],str[maxn*10];
int wa[maxn*10],wb[maxn*10],wv[maxn*10],wss[maxn*10];
int RMQ[maxn],mm[maxn],best[20][maxn];;
int c0(int *r,int a,int b){
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,int b){
if(k==2)
return r[a]<r[b]||(r[a]==r[b]&&c12(1,r,a+1,b+1));
else return r[a]<r[b]||(r[a]==r[b]&&wv[a+1]<wv[b+1]);
}
void sort(int *r,int *a,int *b,int n,int m){
int i;
for(i=0;i<n;i++)wv[i]=r[a[i]];
for(i=0;i<m;i++)wss[i]=0;
for(i=0;i<n;i++)wss[wv[i]]++;
for(i=1;i<m;i++)wss[i]+=wss[i-1];
for(i=n-1;i>=0;i--)
b[--wss[wv[i]]]=a[i];
}
void dc3(int *r,int *sa,int n,int m){
int i,j,*rn=r+n;
int *san=sa+n,ta=0,tb=(n+1)/3,tbc=0,p;
r[n]=r[n+1]=0;
for(i=0;i<n;i++)if(i%3!=0)wa[tbc++]=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<tbc;i++)
rn[F(wb[i])]=c0(r,wb[i-1],wb[i])?p-1:p++;
if(p<tbc)dc3(rn,san,tbc,p);
else for(i=0;i<tbc;i++)san[rn[i]]=i;
for(i=0;i<tbc;i++)if(san[i]<tb)wb[ta++]=san[i]*3;
if(n%3==1)wb[ta++]=n-1;
sort(r,wb,wa,ta,m);
for(i=0;i<tbc;i++)wv[wb[i]=G(san[i])]=i;
for(i=0,j=0,p=0;i<ta&&j<tbc;p++)
sa[p]=c12(wb[j]%3,r,wa[i],wb[j])?wa[i++]:wb[j++];
for(;i<ta;p++)sa[p]=wa[i++];
for(;j<tbc;p++)sa[p]=wb[j++];
}
void da(int n,int m){
for(int i=n;i<n*3;i++)str[i]=0;
dc3(str,sa,n+1,m);
int i,j,k=0;
for(i=0;i<=n;i++)rank[sa[i]]=i;
for(i=0;i<n;i++){
if(k)k--;
j=sa[rank[i]-1];
while(str[i+k]==str[j+k])k++;
height[rank[i]]=k;
}
}
void initRMQ(int n){
for(int i=1;i<=n;i++)RMQ[i]=height[i];
mm[0]=-1;
for(int i=1;i<=n;i++)
mm[i]=((i&(i-1))==0)?mm[i-1]+1:mm[i-1];
for(int i=1;i<=n;i++)best[0][i]=i;
for(int i=1;i<=mm[n];i++)
for(int j=1;j+(1<<i)-1<=n;j++){
int a=best[i-1][j];
int b=best[i-1][j+(1<<(i-1))];
if(RMQ[a]<RMQ[b])best[i][j]=a;
else best[i][j]=b;
}
}
int askRMQ(int a,int b){
int t;
t=mm[b-a+1];
b-=(1<<t)-1;
a=best[t][a];b=best[t][b];
return RMQ[a]<RMQ[b]?a:b;
}
int lcp(int a,int b){
a=rank[a];b=rank[b];
if(a>b)swap(a,b);
return height[askRMQ(a+1,b)];
}
void print(int n){
cout<<"sa[] ";
for(int i=0;i<=n;i++)cout<<sa[i]<<" ";cout<<endl;
cout<<"rank[] ";
for(int i=0;i<=n;i++)cout<<rank[i]<<" ";cout<<endl;
cout<<"height[] ";
for(int i=0;i<=n;i++)cout<<height[i]<<" ";cout<<endl;
}
}DA;
char str[maxn],str1[maxn];
int s[maxn][2];
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int k;
while(cin>>k&&k){
cin>>str>>str1;
int lena=strlen(str);
int lenb=strlen(str1);
int n=0;
for(int i=0;i<lena;i++)DA.str[n++]=str[i];
DA.str[n++]=1;
for(int i=0;i<lenb;i++)DA.str[n++]=str1[i];
DA.da(n,300);
ll tot=0,top=0;
ll sum=0;
for(int i=1;i<=n;i++){
if(DA.height[i]<k)top=0,tot=0;
else{
ll cnt=0;
if(DA.sa[i-1]<lena){
cnt++;tot+=(ll)DA.height[i]-k+1;
}
while(top&&DA.height[i]<=s[top-1][0]){
top--;
tot+=(ll)s[top][1]*(DA.height[i]-s[top][0]);
cnt+=(ll)s[top][1];
}
s[top][0]=DA.height[i];s[top++][1]=cnt;
if(DA.sa[i]>lena)sum+=tot;
}
}
tot=top=0;
for(int i=1;i<=n;i++){
if(DA.height[i]<k)top=0,tot=0;
else{
int cnt=0;
if(DA.sa[i-1]>lena){
cnt++;tot+=(ll)DA.height[i]-k+1;
}
while(top&&DA.height[i]<=s[top-1][0]){
top--;
tot+=(ll)s[top][1]*(DA.height[i]-s[top][0]);
cnt+=(ll)s[top][1];
}
s[top][0]=DA.height[i];s[top++][1]=cnt;
if(DA.sa[i]<lena)sum+=tot;
}
}
cout<<sum<<endl;
}
return 0;
}
「kuangbin帶你飛」專題十八 後綴數組