理論知識
詳見 OI Wiki 。
模板
字尾排序
一切有關字尾陣列問題的必備板子。
求字尾陣列模板題,OI Wiki 有詳解 。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e6+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N];
char s[N];
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
cin>>s+1;
n=strlen(s+1);
init();
for(int i=1;i<=n;i++)
cout<<sa[i]<<' ';
}
可重疊最長重複子串
求 \(height\) 陣列模板題,OI Wiki 有詳解如何求 \(height\) 陣列。
即求 \(\max\limits_{i=1}^n\{height_i\}\) 。
注意 \(height_1=0\) 。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans;
char s[N];
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n);
cin>>s+1;
init();
for(int i=1;i<=n;i++)
ans=max(ans,height[i]);
cout<<ans;
}
應用
\(height\) 陣列基本應用
不同子串個數
所有子串的個數為 \(\dfrac{n(n+1)}{2}\) ,其中重複的有 \(\sum\limits_{i=1}^nheight_i\) ,故答案為 \(\dfrac{n(n+1)}{2}-\sum\limits_{i=1}^nheight_i\) 。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,q,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans;
char s[N];
int sta[N],top,a[N],b[N];
struct aa
{
int l,r,id;
}e[N];
bool cmp(aa a,aa b) {return a.l==b.l?a.r>b.r:a.l<b.l;}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n);
cin>>s+1;
init();
for(int i=1;i<=n;i++)
ans+=height[i];
cout<<n*(n+1)/2-ans;
}
Milk Patterns G
求出現最少 \(k\) 次的子串的最大長度。
出現至少 \(k\) 次說明字尾排序後至少有連續 \(k\) 個字尾以這個子串為公共串。
即求出沒連續 \(k-1\) 個 \(height_i\) 的最小值,這些最小值中的最大值即為所求。
可以用單調佇列 \(O(n)\) 解決,當然及時不用也足以 \(AC\) 。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e6+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,K,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans;
int s[N];
deque<int>q;
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=1000000,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n),read(K);
K--;
for(int i=1;i<=n;i++) read(s[i]);
init();
for(int i=1;i<=n;i++)
{
while(!q.empty()&&height[q.front()]>=height[i]) q.pop_front();
q.push_front(i);
while(!q.empty()&&q.back()<=i-K) q.pop_back();
if(i>=K) ans=max(ans,height[q.back()]);
}
write(ans);
}
結合 \(ST\) 表
我們知道 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以透過 \(ST\) 表維護 \(RMQ\) 是很常見的。
很多時候 \(ST\) 表起的只是一個輔助作用,所以好多結合 \(ST\) 表的題還會結合別的。
Yet Another LCP Problem
對於每一組給定的 \(a,b\) 陣列,不妨將其拼成 \(c\) 陣列。
我們定義 \(ans_x=\sum\limits_{i=1}^{len_x}\sum\limits_{j=1}^{len_x}LCP(s_{x_i\sim n},s_{x_j\sim n})\) ,那麼有:
至於為什麼 \(\dfrac{1}{2}\) 如下:
我們知道 \(\sum\limits_{i=1}^{len_x}\sum\limits_{j=i+1}^{len_x}LCP(s_{x_i\sim n},s_{x_j\sim n})=\dfrac{\sum\limits_{i=1}^{len_x}\sum\limits_{j=1}^{len_x}LCP(s_{x_i\sim n},s_{x_j\sim n})}{2}\) ,不妨直接另 \(ans_x=\sum\limits_{i=1}^{len_x}\sum\limits_{j=i+1}^{len_x}LCP(s_{x_i\sim n},s_{x_j\sim n})\) ,那麼求 \(ans_c-ans_a-ans_b\) 即可。
結合單調棧求 \(ans_x\) :
先將 \(x\) 陣列按照 \(rk[x_1]<rk[x_2]\) 排序,使其符合單調性。
定義 \(val_i=LCP(x_{i-1},x_i)\) ,類似 \(luogu~P4248\) 差異 這道題,去計算 \(val_i\) 的貢獻,實際上 \(val\) 的定義和 \(height\) 是類似的,因為 \(x_i\) 並不連續,所以需要這樣操作,顯然有 \(val_1=0\) 。
接下來單調棧維護的過程與 \(luogu~P4248\) 差異 是類似的,處理出最靠右的 \(0\leq l<i,val_l<val_i\) 的位置,以及最靠左的 \(i<r\leq n,val_r<val_i\) 的位置,那麼 \(l\sim r\) 這一段的 \(LCP\) 均為 \(val_i\) ,依據乘法原理, \(val_i\) 的貢獻就是 \(val_i\times (i-l)\times (r-i)\) ,不妨定義 \(l_i=i-l,r_i=r-i\) 。
那麼 \(ans_x=\sum\limits_{i=1}^{len_x}val_i\times l_i\times r_i\) 。
至於 \(LCP\) ,可以用 \(ST\) 表維護。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=4e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,ans,sum,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N],a[N],b[N],c[N],mi[N][30],val[N];
char s[N],t[N];
int sta[N],top;
void clean()
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n)
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
void RMQ()
{
memset(mi,0x3f,sizeof(mi));
for(int i=1;i<=n;i++)
mi[i][0]=height[i];
for(int j=1;j<=log2(n);j++)
for(int i=1;i<=n-(1<<j)+1;i++)
mi[i][j]=min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
}
int ask(int l,int r)
{
int t=log2(r-l+1);
return min(mi[l][t],mi[r-(1<<t)+1][t]);
}
bool cmp(int a,int b) {return rk[a]<rk[b];}
int calc(int x[],int len)
{
sort(x+1,x+1+len,cmp);
for(int i=2;i<=len;i++)
if(x[i]==x[i-1])
val[i]=n-x[i]+1;
else
val[i]=ask(rk[x[i-1]]+1,rk[x[i]]);
sum=ans=top=0;
for(int i=1;i<=len;i++)
{
while(val[sta[top]]>val[i]) top--;
l[i]=i-sta[top];
sta[++top]=i;
}
val[len+1]=-1,sta[++top]=len+1;
for(int i=len;i>=1;i--)
{
while(val[sta[top]]>=val[i]) top--;
r[i]=sta[top]-i;
sta[++top]=i;
}
for(int i=1;i<=len;i++)
ans+=l[i]*r[i]*val[i];
return ans;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n),read(m);
cin>>s+1;
init(s,n);
RMQ();
for(int i=1,len1,len2;i<=m;i++)
{
read(len1),read(len2);
for(int j=1;j<=len1;j++)
read(a[j]),
c[j]=a[j];
for(int j=1;j<=len2;j++)
read(b[j]),
c[len1+j]=b[j];
cout<<calc(c,len1+len2)-calc(a,len1)-calc(b,len2)<<endl;
}
}
相似子串
我們發現所有子串的左端點都在字尾陣列中出現過,問題是考慮右端點。
發現例如字尾 abcd
,可以將其分為 a
、ab
、abc
、abcd
四個子串,若其與上一個字尾的 \(LCP\) 為 ab
,則新的子串就剩下 abc
、abcd
。
由此可得每個字尾對子串個數的貢獻為 \(len_i-height_i\) ,如此得來所有的子串已經是排好序的。
不妨使用字首和維護,利用二分查詢出第 \(i\) 個子串處於哪一個字尾中,定義為 \(pos_i\) 。
對於每次查詢 \(x,y\) ,處理出 \(pos_x,pos_y\) ,那麼 \(len_x=height_{pos_x}+x-sum_{pos_x-1}\) ,\(leny\) 同理,那麼很容易求出子串 \(x,y\) 的公共字首 \(=\min(\min(len_x,len_y),LCP(sa_{pos_x},sa_{pos_y}))\) ,當然若 \(pos_x=pos_y\) ,其只能 \(=\min(len_x,len_y)\) ,可以用 \(ST\) 表維護 \(LCP\) 。
問題來到如何處理公共字尾,不難想到建一個反串類似的維護,而問題是子串 \(x,y\) 在反串中的 \(pos\) 是多少。
定義 \(pos'_i\) 表示子串 \(i\) 在反串中處於哪一個字尾中,有 \(pos'_x=rk_{n-(sa_{pos_x}+len_x-1)+1}\) ,\(pos'_y\) 同理,即該子串右端點的 \(rk\) ,再類似上面維護即可。
雖然這樣求 \(pos'\) 不一定絕對準確,但是他能夠求出這樣的 \(pos'\) 說明他在 \(pos'\) 中一定是合法的,所以這樣去求也是沒有任何問題的,樣例 ``ababa 就是個很好的例子,
aba``` 的 \(pos'\) 應該為 \(2\) ,但求出來的 \(pos'=3\) ,然而對答案沒有影響,可以手動模擬理解一下。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,ans,sa[N][2],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N][2],sum[N][2],ls[N],mi[2][N][30];
char s[N],t[N];
void clean()
{
memset(rk,0,sizeof(rk));
// memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
// memset(height,0,sizeof(height));
memset(ls,0,sizeof(ls));
}
void count_sort(int n,int m,bool d)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]][d]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n,bool d)
{
clean();
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m,d);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i][d]>w)
num++,
id[num]=sa[i][d]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m,d);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i][d]]!=oldrk[sa[i-1][d]]||oldrk[sa[i][d]+w]!=oldrk[sa[i-1][d]+w]),
rk[sa[i][d]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1][d]+k]) k++;
height[rk[i]][d]=k;
}
for(int i=1;i<=n;i++)
ls[i]=n-sa[i][d]+1-height[i][d],
sum[i][d]=sum[i-1][d]+ls[i];
}
void RMQ(bool d)
{
memset(mi[d],0x3f,sizeof(mi[d]));
for(int i=1;i<=n;i++)
mi[d][i][0]=height[i][d];
for(int j=1;j<=log2(n);j++)
for(int i=1;i<=n-(1<<j)+1;i++)
mi[d][i][j]=min(mi[d][i][j-1],mi[d][i+(1<<(j-1))][j-1]);
}
int LCP(int l,int r,bool d)
{
l++;
int t=log2(r-l+1);
return min(mi[d][l][t],mi[d][r-(1<<t)+1][t]);
}
int ask(int x,int y,bool d)
{
int l=1,r=n,mid,pos_x,pos_y;
while(l<=r)
{
mid=(l+r)>>1;
if(sum[mid][0]<x) l=mid+1;
if(sum[mid][0]>x) r=mid-1,pos_x=mid;
if(sum[mid][0]==x)
{
pos_x=mid;
break;
}
}
l=1,r=n;
while(l<=r)
{
mid=(l+r)>>1;
if(sum[mid][0]<y) l=mid+1;
if(sum[mid][0]>y) r=mid-1,pos_y=mid;
if(sum[mid][0]==y)
{
pos_y=mid;
break;
}
}
int len_x=height[pos_x][0]+x-sum[pos_x-1][0],len_y=height[pos_y][0]+y-sum[pos_y-1][0];
if(d==1)
pos_x=rk[n-(sa[pos_x][0]+len_x-1)+1],
pos_y=rk[n-(sa[pos_y][0]+len_y-1)+1];
if(pos_x>pos_y) swap(pos_x,pos_y);
return pos_x==pos_y?min(len_x,len_y):min(min(len_x,len_y),LCP(pos_x,pos_y,d));
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n),read(m);
cin>>s+1;
for(int i=1;i<=n;i++)
t[n-i+1]=s[i];
init(s,n,0),init(t,n,1);
RMQ(0),RMQ(1);
for(int i=1,x,y;i<=m;i++)
{
read(x),read(y);
if(x>sum[n][0]||y>sum[n][0]) puts("-1");
else cout<<ask(x,y,0)*ask(x,y,0)+ask(x,y,1)*ask(x,y,1)<<endl;
}
}
結合單調棧
因為 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以很多時候為了最佳化複雜度去計算 \(height_i\) 的貢獻時需要結合單調棧。
通常思路有兩種,本人主要採取類似於 廣告印刷 這道題,處理出每個 \(height_i\) 能管轄到的 \(l,r\) ,即區間 \([l\sim r]\) 中的最小值為 \(height_i\) ,那麼通常此時 \(height_i\) 能做的貢獻就是 \((i-l+1)\times (r-i+1)\times height_i\) ,依據乘法原理。
差異
結合單調棧板子題。
對於 \(\sum\limits_{i\leq i<j\leq n}len(T_i)+len(T_j)-2\times LCP(T_i,T_j)\) 這個式子,將其分為兩部分計算。
不難計算出 \(\sum\limits_{1\leq i<j\leq n}len(T_i)+len(T_j)=\dfrac{n\times(n-1)\times (n+1)}{2}\) 。首先有 \(n\) 個字串,那麼會產生 \(\dfrac{n\times (n-1)}{2}\) 對組合,我們知道每個字尾的長度平均值 \(=\dfrac{\sum\limits_{i=1}^nlen_{T_i}}{n}=\dfrac{n+1}{2}\) ,那麼兩個字尾長度加一起就是 \(n+1\) ,故答案為 \(\dfrac{n\times(n-1)\times (n+1)}{2}\) 。
問題主要是如何求 \(\sum\limits_{1\leq i<j\leq n}LCP(T_i,T_j)\) 。
\(\begin{aligned} \sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{i \sim |s|,j \sim |s|}) &=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{sa_{i} \sim |s|,sa_{j} \sim |s|}) \\&=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\min\limits_{k=i+1}^{j} \{ height_{k} \} \\ &=\sum\limits_{i=2}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}-\sum\limits_{i=1}^{n}\min\limits_{j=1}^{i} \{ height_{j} \} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}-\sum\limits_{i=1}^{n}0 \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \} \end{aligned}\)
根據 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以很多時候為了最佳化複雜度去計算 \(height_i\) ,考慮計算每個 \(height_i\) 的貢獻,於是用到單調棧。
透過單調棧,處理出 \(\max\limits_{0\leq l<i\&height_l<height_i}\{l\}\) 以及 \(\min\limits_{i<r\leq n+1\&height_r<height_i}\{r\}\) ,那麼 \(height_i\) 的貢獻就是 \((i-l+1)\times(r-i+1)\times height_i\) ,不妨設 \(l_i=i-l+1,r_i=r-i+1\) ,那麼其貢獻就是 \(l_i\times r_i\times height_i\) 。
那麼最後答案就是 \(\dfrac{n\times(n-1)\times (n+1)}{2}-2\times \sum\limits_{i=1}^nl_i\times r_i\times height_i\) 。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=5e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],ans,l[N],r[N];
char s[N];
int sta[N],top;
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
cin>>s+1;
n=strlen(s+1);
init();
for(int i=1;i<=n;i++)
{
while(height[sta[top]]>height[i]) top--;
l[i]=i-sta[top];
sta[++top]=i;
}
sta[++top]=n+1,height[n+1]=-1;
for(int i=n;i>=1;i--)
{
while(height[sta[top]]>=height[i]) top--;
r[i]=sta[top]-i;
ans-=2ll*l[i]*r[i]*height[i];
sta[++top]=i;
}
ans+=1ll*n*(n-1)*(n+1)/2;
cout<<ans;
}
找相同字元
設兩個串為 \(t_1,t_2\) ,那麼將兩個串拼起來成為 \(s\) ,中間用分隔符 #
隔開,定義 \(ans_x\) 表示與差異所求一樣的東西,那麼答案就是 \(ans_s-ans_{t_1}-ans_{t_2}\) 。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=4e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m2,m1,ans,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N];
char s[N],t2[N],t1[N];
int sta[N],top;
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n)
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
int calc(char s[],int len)
{
init(s,len);
ans=top=0;
for(int i=1;i<=len;i++)
{
while(height[sta[top]]>height[i]) top--;
l[i]=i-sta[top];
sta[++top]=i;
}
int x=height[len+1];
sta[++top]=len+1,height[len+1]=-1;
for(int i=len;i>=1;i--)
{
while(height[sta[top]]>=height[i]) top--;
r[i]=sta[top]-i;
ans+=l[i]*r[i]*height[i];
sta[++top]=i;
}
height[len+1]=x;
return ans;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
cin>>t1+1>>t2+1;
m1=strlen(t1+1),m2=strlen(t2+1);
for(int i=1;i<=m1;i++)
s[i]=t1[i];
s[m1+1]='#';
for(int i=1;i<=m2;i++)
s[m1+1+i]=t2[i];
n=m1+m2+1;
cout<<calc(s,n)-calc(t2,m2)-calc(t1,m1);
}
Yet Another LCP Problem
上面已有。
多個串拼合
大多數找不同字串的公共串的問題都需要將多個字串拼起來處理。
字元加密
將 \(s\) 複製一遍變成 \(ss\) ,就轉化為字尾排序問題。
若 \(sa_i\leq \dfrac{n}{2}\) ,就輸出 \(s_{sa_i+\frac{n}{2}-1}\) 。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N];
char s[N];
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init()
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
cin>>s+1;
n=strlen(s+1);
for(int i=1;i<=n;i++)
s[i+n]=s[i];
n*=2;
init();
for(int i=1;i<=n;i++)
if(sa[i]<=n/2)
cout<<s[sa[i]+n/2-1];
}
公共串
將 \(t_1\sim t_n\) 拼起來得到 \(s=t_1t'_1t_2t'_2…t_nt'_n\) ,其中 \(t'_i\) 表示分隔符,每個分隔符不相同。
那麼對於每個區間 \([l,r]\) 中對於任意一個 \(t_i\) 均有一個字尾的排名在該區間中,則答案為 \(\max\limits_{1\leq l<r\leq |s|}\{\min\limits_{i=l+1}^r\{height_i\}\}\) 。
使用雙指標加單調佇列維護即可。
接下來詳細解釋怎麼統計是否每個 \(t_i\) 均有字尾的排名存在於區間 \(|l,r|\) 裡。
定義兩個函式 \(add,del\) ,分別處理加入一個元素與去除一個元素的情況。
先處理出每個 \(t_i\) 對應的 \(l_i,r_i\) ,即左右斷點,在此之內的每個元素使 \(c_{rk_j}=i,l_i\leq j\leq r_i\) 。
定義 \(vis_i\) 記錄 \(i\) 出現的次數,當插入一個指標 \(i\) 時,先判斷是否為分隔符,若不是則 \(vis_{c_i}++\) ,\(sum+=[vis_{c_i}=1]\) ,同理 \(del\) 函式對應 \(vis_{c_i}--\) ,\(sum-=[vis_{c_i}=0]\) 。
那麼當 \(sum=n\) 時,即 \(l,r\) 滿足條件。
同時因為 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以我們希望這個區間儘可能小,那麼列舉 \(r\) ,同時當 \(l,r\) 滿足條件的前提下另 \(l\) 儘可能大,從而使處理答案的複雜度控制在 \(O(n)\) 。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=1e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,ans,sum,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N],c[N],v[N],L[N],R[N];
char s[N],t[N];
deque<int>q;
void clean()
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(char s[],int n)
{
int m=127,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
void add(int x,int &sum)
{
if(c[x]==0) return ;
v[c[x]]++;
sum+=(v[c[x]]==1);
}
void del(int x,int &sum)
{
if(c[x]==0) return ;
v[c[x]]--;
sum-=(v[c[x]]==0);
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(m);
for(int i=1;i<=m;i++)
{
cin>>t+1;
int len=strlen(t+1);
L[i]=n+1;
for(int j=1;j<=len;j++)
s[++n]=t[j];
R[i]=n;
s[++n]=i+'#';
}
init(s,n);
for(int i=1;i<=m;i++)
for(int j=L[i];j<=R[i];j++)
c[rk[j]]=i;
add(1,sum);
for(int l=1,r=2;r<=n;r++)
{
while(!q.empty()&&height[q.front()]>=height[r])
q.pop_front();
q.push_front(r);
add(r,sum);
if(sum>=m)
{
while(sum>=m&&l<=r-1)
del(l,sum),
l++;
l--;
add(l,sum);
}
while(!q.empty()&&q.back()<=l)
q.pop_back();
if(sum==m)
ans=max(ans,height[q.back()]);
}
cout<<ans;
}
Sandy 的卡片
查分後變為 公共串 ,因為差分後長度會 \(-1\) ,所以最後答案要 \(+1\) 。
點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=2e5+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,ans,sum,sa[N],rk[N],id[N],oldrk[N],cnt[N],key[N],height[N],l[N],r[N],c[N],v[N],L[N],R[N];
int s[N],t[N];
deque<int>q;
void clean()
{
memset(rk,0,sizeof(rk));
memset(sa,0,sizeof(sa));
memset(id,0,sizeof(id));
memset(oldrk,0,sizeof(rk));
memset(cnt,0,sizeof(cnt));
memset(key,0,sizeof(key));
memset(height,0,sizeof(height));
memset(l,0,sizeof(l));
memset(r,0,sizeof(r));
}
void count_sort(int n,int m)
{
memset(cnt,0,sizeof(cnt));
for(int i=1;i<=n;i++)
cnt[key[i]]++;
for(int i=1;i<=m;i++)
cnt[i]+=cnt[i-1];
for(int i=n;i>=1;i--)
sa[cnt[key[i]]]=id[i],
cnt[key[i]]--;
}
void init(int s[],int n)
{
int m=5000,tot=0,num=0;
for(int i=1;i<=n;i++)
rk[i]=(int)s[i],
id[i]=i,
key[i]=rk[id[i]];
count_sort(n,m);
for(int w=1;w<n&&tot!=n;w<<=1,m=tot)
{
num=0;
for(int i=n;i>=n-w+1;i--)
num++,
id[num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>w)
num++,
id[num]=sa[i]-w;
for(int i=1;i<=n;i++)
key[i]=rk[id[i]];
count_sort(n,m);
for(int i=1;i<=n;i++)
oldrk[i]=rk[i];
tot=0;
for(int i=1;i<=n;i++)
tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]),
rk[sa[i]]=tot;
}
for(int i=1,k=0;i<=n;i++)
{
if(rk[i]==0) continue;
if(k) k--;
while(s[i+k]==s[sa[rk[i]-1]+k]) k++;
height[rk[i]]=k;
}
}
void add(int x,int &sum)
{
if(c[x]==0) return ;
v[c[x]]++;
sum+=(v[c[x]]==1);
}
void del(int x,int &sum)
{
if(c[x]==0) return ;
v[c[x]]--;
sum-=(v[c[x]]==0);
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(m);
for(int i=1,len;i<=m;i++)
{
cin>>len;
L[i]=n+1;
for(int j=1;j<=len;j++)
{
read(t[j]);
if(j>=2)
s[++n]=2000+t[j]-t[j-1];
}
R[i]=n;
s[++n]=i+2000;
}
init(s,n);
for(int i=1;i<=m;i++)
for(int j=L[i];j<=R[i];j++)
c[rk[j]]=i;
add(1,sum);
for(int l=1,r=2;r<=n;r++)
{
while(!q.empty()&&height[q.front()]>=height[r])
q.pop_front();
q.push_front(r);
add(r,sum);
if(sum>=m)
{
while(sum>=m&&l<=r-1)
del(l,sum),
l++;
l--;
add(l,sum);
}
while(!q.empty()&&q.back()<=l)
q.pop_back();
if(sum==m)
ans=max(ans,height[q.back()]);
}
cout<<ans+1;
}
找相同字元
上面已有,由於涉及將兩個串拼起來的思路,所以也放在了這裡。
Yet Another LCP Problem
上面已有,同樣的涉及將兩個串拼起來。
結合分塊與莫隊
優秀的拆分
列舉連續串的長度 \(|s|\) ,按照 \(|s|\) 對整個串進行分塊,對相鄰兩個塊進行 \(LCP,LCS\) 查詢。
由於沒有學莫隊,暫時還沒有寫。
具體可見 [2009] 字尾陣列——處理字串的有力工具。
結合單調佇列
因為 \(LCP(sa_l,sa_r)=\min\limits_{i=l+1}^r\{height_i\}\) ,所以類似於求帶限制的最長公共子串問題時常結合單調佇列,常見的形式為求 \(\max\limits_{1\leq l<r\leq |s|}\{\min\limits_{i=l+1}^r\{height_i\}\}\) ,且同時常存在隊內長度的限制。
下面的例題前面都已經涉及,就只沾題目了。
Milk Patterns G
公共串
Sandy 的卡片
結合並查集
品酒大會
暫時還沒有打,待會兒補上。