AB213 F Common Prefixes 題解
AB213 F Common Prefixes 題解
題面
定義 \(f(X,Y)\) 為字串 \(X,Y\) 的公共字首長度。
給定長度為 \(N\) 的字串 \(S\),設 \(S_i\) 表示從第 \(i\) 個字元開始的 \(S\) 的字尾。
計算出:對於 \(k=1,2,...,N\),\(f(S_k,S_1)+f(S_k,S_2)+f(S_k,S_3)+...+f(S_k,S_N)\) 的值。
輸入格式
第一行一個整數 \(N(1≤N≤106)\)
第二行為長度 \(N\) 的字串 \(S\)。
輸出格式
輸出 \(N\) 行,第 \(k\) 行,表示 \(f(S_k,S_1)+f(S_k,S_2)+f(S_k,S_3)+...+f(S_k,S_N)\)
樣例輸入
3
abb
樣例輸出
3
3
2
樣例解釋
\(S1=abb,S2=bb,S3=b\)
\(For\ k=1\ f(S1,S1)+f(S1,S2)+f(S1,S3)=3+0+0=3\)
\(For\ k=2\ f(S2,S1)+f(S2,S2)+f(S2,S3)=0+2+1=3\)
\(For\ k=3\ f(S3,S1)+f(S3,S2)+f(S3,S3)=0+1+1=2\)
解題
演算法:SA+LCP+單調棧
前置知識:字尾陣列學習筆記
顯然,\(f\) 和 \(LCP\) 的含義是相同的
我們知道,\(LCP(i,j)=\min_{i\leq k\leq j-1}\{LCP(k,k+1)\}\)
所以,問題被轉化成這樣:
Problem:
給定一個長為 \(n\) 的序列 \(a_i\),
令 \(g(i,j)=\min_{i\leq k\leq j-1}\{a_k\}\)
對於每一個 \(k=1\dots n\), 求值: \(\sum_{i=1}^n g(i,k)\)
我們考慮分開求,分成 \(\sum_{i=1}^{k-1}g(i,k)+g(k,k)+\sum_{i=k+1}^ng(i,k)\)
顯然,第一個和第二個本質相同,中間那個就是 \(Suf(k)\) 的長度。
令 \(pre_k=\sum_{i=1}^{k-1}g(i,k)\) ,觀察:
\[\begin{align} pre_k&=g(1,k)+g(2,k)+g(3,k)+\dots+g(k-1,k)\\ pre_{k+1}&=g(1,k+1)+g(2,k+1)+g(3,k+1)+\dots+g(k-1,k)+a_{k}\\ &=\min(g(1,k),a_k)+\min(g(2,k),a_k)+\dots +\min(g(k-1,k),a_k)+a_k \end{align} \]可以發現,二者之間存在遞推關係
如何處理取 \(\min\) 操作?
考慮 \(a_k\) 的貢獻範圍:\((前面第一個大於 a_k的位置,k+1]\),
所以,我們可以採用 單調棧 (其實程式碼比文字更好懂)
另外,有一道相似的題目:[AHOI2013]差異 - 洛谷 ,其中也有單調棧的思想
程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+5;
int n,m,top;
int tax[N],rk[N],sa[N],tp[N],h[N];
int pre[N],suf[N],stk[N];
char s[N];
void radix_sort(){
for(int i=1;i<=m;++i)tax[i]=0;
for(int i=1;i<=n;++i)tax[rk[i]]++;
for(int i=1;i<=m;++i)tax[i]+=tax[i-1];
for(int i=n;i>=1;--i)sa[tax[rk[tp[i]]]--]=tp[i];
}
void suffix_sort(){
m=75;
for(int i=1;i<=n;++i)
rk[i]=s[i]-'a'+1,tp[i]=i;
radix_sort();
for(int w=1,p=0;w<n;m=p,w<<=1,p=0){
for(int i=1;i<=w;++i)tp[++p]=n-w+i;
for(int i=1;i<=n;++i)if(sa[i]>w)tp[++p]=sa[i]-w;
radix_sort();swap(tp,rk);
rk[sa[1]]=p=1;
for(int i=2;i<=n;++i)
rk[sa[i]]=(tp[sa[i-1]]==tp[sa[i]]&&tp[sa[i-1]+w]==tp[sa[i]+w])?p:++p;
}
}
void geth(){
int j,k=0;
for(int i=1;i<=n;++i){
if(k)--k;
int j=sa[rk[i]-1];
while(s[i+k]==s[j+k])++k;
h[rk[i]]=k;
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n;cin>>(s+1);
suffix_sort();geth();
stk[top=0]=1;//維護一個單調上升的棧
for(int i=2;i<=n;++i){
pre[i]=pre[i-1];
while(top&&h[i]<h[stk[top]])
pre[i]-=(stk[top]-stk[top-1])*h[stk[top--]];//減去之前的貢獻
pre[i]+=(i-stk[top])*h[i];
stk[++top]=i;
}
stk[top=0]=n+1;
for(int i=n;i>=2;--i){
suf[i]=suf[i+1];
while(top&&h[i]<h[stk[top]])
suf[i]-=(stk[top-1]-stk[top])*h[stk[top--]];
suf[i]+=(stk[top]-i)*h[i];
stk[++top]=i;
}
for(int i=1;i<=n;++i)
cout<<pre[rk[i]]+suf[rk[i]+1]+n-i+1<<"\n";
return 0;
}