$\newcommand{\O}{\mathrm{O}}$

文字列 $S$ が与えられたとき $0 \le i < |S|$ について $S$ と $S[i:]$ の最長共通接頭辞の長さを求める線形時間アルゴリズム(参考).
(注) 以下の説明より (参考) の記事を見たほうがわかりやすい.
各ループの初めにおいて $i$ は今 $S$ と $S[i:]$ の最長共通接頭辞を求めていることを意味しており, また $j$ は $S$ と $S[i:]$ のうち先頭 $j$ 文字についてはすでに一致が確定していることを表す.
$1$ つ目の while 文では愚直に $S$ と $S[i:]$ の最長共通接頭辞を、確定していない $j + 1$ 番目以降の文字について愚直に一致を調べることで求めている. しかしこれだけでは線形にならない.
ここで $k(\ge 1)$ について $S$ と $S[i+k:]$ の最長共通接頭辞は $S$ と $S[i:]$ の先頭 $j$ 文字が一致することから $S$ と $S[k:]$ の最長共通接頭辞と一致することを用いる.
ただしこれは $k + $ ($S$ と $S[k:]$ の最長共通接頭辞の長さ) $ < j$ の場合の話である. 最後に重要なこととして $k$ がそのような条件を満たさなくなった場合でも
$S$ と $S[i + k:]$ の先頭 $j - k$ 文字は一致することが確定するのでループの最後において $j$ を $j - k$ に更新する.
最後に計算量を考える. まず $1$ つ目の while ループは $i + j$ の値に注目することで bound できる. $i + j$ は 1 つ目のループを回るごとに $1$ 増え, $2$ つ目のループでは変化しない. $i + j$ は $|S|$ 以下なので $1$ つ目の while ループを回る回数は高々 $|S|$ 回.
$2$ つ目の while ループは $i$ の値に注目することで bound できる. $i$ は $1$ つ目のループでは変化せず $2$ つ目の while ループでは回るごとに $1$ 増える. $i$ は $|S|$ 以下なので $2$ つ目の while ループを回る回数は高々 $|S|$ 回. よって合わせて $\O (|S|)$ となる.
時間計算量: $\O (|S|)$
時間計算量(オンライン版): add ならし $\O (1)$ query $\O(1)$
void z_algorithm(const string& S, vector<int>& res)
{
int sz = (int)S.size();
res.resize(sz, sz);
int i = 1, j = 0;
while(i < sz){
while(i+j < sz && S[j] == S[i+j]) j++;
res[i] = j;
if(j == 0){
i++;
continue;
}
int k = 1;
while(i+k < sz && k+res[k] < j){
res[i+k] = res[k], k++;
}
i += k; j -= k;
}
}
class OnlineZalgo {
private:
string s;
vector<int> arr;
queue<int> unconfirm;
vector<vector<int> > info;
public:
OnlineZalgo() : arr(1), info(1){}
void add(char c){
s.push_back(c);
if((int)s.size() == 1) return;
info.push_back(vector<int>());
int nx = -1;
while(!unconfirm.empty()){
const int id = unconfirm.front();
if(arr[id] >= 0){
unconfirm.pop();
}else if(s[(int)s.size() - 1 - id] != c){
unconfirm.pop();
arr[id] += (int)s.size() - 1;
info.back().push_back(id);
}else{
nx = (int)s.size() - 1 - id;
break;
}
}
if(nx >= 0){
for(const auto& e : info[nx]){
const int id = (int)s.size() - 1 - nx + e;
arr[id] += (int)s.size() - 1;
info.back().push_back(id);
}
}
arr.push_back(0);
if(s[0] == c){
unconfirm.push((int)s.size() - 1);
arr.back() = -(int)s.size() + 1;
}
}
// s と s[index:] の最長共通接頭辞の長さを返す
int query(int index){
assert(index < (int)s.size());
if(index == 0) return (int)s.size();
return (arr[index] >= 0) ? arr[index] : (arr[index] + (int)s.size());
}
};
Atcoder : Prefix Suffix Free 提出コード