后缀数组 SA
膜拜 zxy,1h 学会 SA。这玩意真的好绕啊 >w<
给定一个字符串 \(S\),设 \(S(l,r)\) 表示 \(S_l\dots S_r\) 组成的字符串,\(s(i)\) 表示 \(S(i,n)\)。
将 \(s(1),\dots,s(n)\) 排序,设 \(sa[i]\) 表示排名为 \(i\) 的字符串为 \(s(sa[i])\)。
我们顺次考虑按照前 \(1,2,4,8,\dots,k\) 位 排好序的情况,那我们的问题是 how 倍增求解 qwq
我们知道 \(S(i,i+k-1)\) 和 \(S(i+k,i+2*k-1)\) 在长度为 \(k\) 下的排名,想要合并显然是先看前者再看后者即可,设前者为第一关键字,后者为第二关键字,一次合并 \(O(n)\),总的复杂度 \(O(n\log n)\)。
\(sa(i)\) 表示排名为 \(i\) 的字符串在原串中的起始位置。
\(x(i)\) 表示排名为 \(i\) 的第一关键字,\(y(i)\) 是排名为 \(i\) 的第二关键字。
n=strlen(s+1), m=122; // m 是字符种类数
up(i,1,n) x[i]=s[i], c[x[i]]++;
up(i,2,m) c[i]+=c[i-1];
up(i,1,n) sa[c[x[i]]--]=i; // 按 s(i,i) 缔造初始 SA
for(int k=1; k<=n; k<<=1) { // 倍增长度
int num=0;
up(i,n-k+1,n) y[++num]=i; // 这些位置的第二关键字为 null,是极小的,先对其进行赋值 o.O
up(i,1,n) if(sa[i]>k) y[++num]=sa[i]-k; // 以 sa[i] 作为第二关键字的情况
up(i,1,m) c[i]=0;
up(i,1,n) c[x[i]]++;
up(i,2,m) c[i]+=c[i-1];
down(i,n,1) sa[c[x[y[i]]]--]=y[i]; // 重构排名 SA,y 越大的排名越大
up(i,1,n) y[i]=x[i], x[i]=0; // from x to y ~
num=1, x[sa[1]]=1;
up(i,2,n) {
if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]) x[sa[i]]=num;
else x[sa[i]]=++num;
} // 数一下排名相同的多少个
if(num==n) break; m=num; // 更新种类数
}
up(i,1,n) cout << sa[i] << ' '; // perfect!