SA-IS

https://local.ugene.unipro.ru/tracker/secure/attachment/12144/Linear+Suffix+Array+Construction+by+Almost+Pure+Induced-Sorting.pdf
elementi-bioinformatica/Two Efficient Algorithms for Linear Time Suffix Array Construction.pdf at master · AlgoLab/elementi-bioinformatica · GitHub

SA-ISの論文2つだが、2つめのほうがわかりやすいし、実装例もあるので圧倒的にこっちをオススメする。

他にも役立ちそうなサイトをあげとく。
https://topcoder.g.hatena.ne.jp/iwiwi/20110720/1311168147
http://shogo82148.github.io/homepage/memo/algorithm/suffix-array/sa-is.html
http://blog.beam2d.net/2011/08/suffix-array-sa-is.html
http://gasin.hatenadiary.jp/entry/2017/06/08/101525

大まかなアルゴリズムの手順は、

1. Induced Sortを使ってLMS-substringをsortする。
2. LMS-substringのindexに関するSAを構築する。
3. 構築したSAを使って求めたい文字列に対するSAを構築する。

まずSuffix Arrayアルゴリズムに関する前提知識がないとInduced Sortで引っかかる。Induced Sortもかなり高度なことをやっていて論文だけでは到底理解できない。
LMSとついている言葉が大量に出てきて(LMS-substring, LMS-suffix, LMS-prefix)これも混乱を招く要因。

論文を読むのは初めてではないといえどかなり苦戦した。
次からは言葉の定義をちゃんと抑えながら、適宜ググるのも忘れず、読み進めるようにしたい。

以下自分で実装したSA-IS(論文のコードを扱いやすくしただけだが)

namespace SA { //please init, don't use 0 as character
	vi bkt;
	inline bool isLMS(const vi& t, int i) {
		if(i > 0 && t[i] && !t[i - 1]) return true;
		else return false;
	}
	void getBuckets(const vi& s, int K, bool end) {
		int sum = 0, n = (int)s.size();
		fill(bkt.begin(), bkt.begin() + K, 0);
		rep(i, 0, n) bkt[s[i]]++;
		rep(i, 0, K) {
			sum += bkt[i];
			bkt[i] = end ? sum : sum - bkt[i];
		}
	}
	void induceSAl(const vi& t, const vi& s, vi& SA, int K) {
		int n = (int)SA.size();
		getBuckets(s, K, false);
		rep(i, 0, n) {
			int j = SA[i] - 1;
			if(j >= 0 && !t[j]) SA[bkt[s[j]]++] = j;
		}
	}
	void induceSAs(const vi& t, const vi& s, vi& SA, int K) {
		int n = (int)SA.size();
		getBuckets(s, K, true);
		rer(i, n, 0) {
			int j = SA[i] - 1;
			if(j >= 0 && t[j]) SA[--bkt[s[j]]] = j;
		}
	}
	vi get_sa_impl(const vi& s, int K) {
		int n = (int)s.size();
		vi t(n, 0);
		t[n - 1] = 1; t[n - 2] = 0;
		rer(i, n - 2, 0) 
			if(s[i] < s[i + 1] || (s[i] == s[i + 1] && t[i + 1] == 1)) t[i] = 1;
		getBuckets(s, K, true);
		vi SA(n, -1);
		rep(i, 1, n) if(isLMS(t, i)) SA[--bkt[s[i]]] = i;
		induceSAl(t, s, SA, K); induceSAs(t, s, SA, K);
		int n1 = 0;
		rep(i, 0, n) if(isLMS(t, SA[i])) SA[n1++] = SA[i];
		fill(SA.begin() + n1, SA.end(), -1);
		int name = 0, prev = -1;
		rep(i, 0, n1) {
			int pos = SA[i]; bool diff = false;
			rep(d, 0, n) {
				if(prev == -1 || s[pos + d] != s[prev + d] || t[pos + d] != t[prev + d]) {
					diff = true; break;
				}
				else if(d > 0 && (isLMS(t, pos + d) || isLMS(t, prev + d))) break;
			}
			if(diff) { name++; prev = pos; }
			pos = (pos % 2 == 0) ? pos / 2 : (pos - 1) / 2;
			SA[n1 + pos] = name - 1;
		}
		vi s1(n1, -1), SA1(n1, -1), p1(n1, -1);
		int at = 0;
		rep(i, n1, n) if(SA[i] >= 0) s1[at++] = SA[i];
		if(name < n1) SA1 = get_sa_impl(s1, name);
		else rep(i, 0, n1) SA1[s1[i]] = i;
		getBuckets(s, K, true);
		at = 0;
		rep(i, 1, n) if(isLMS(t, i)) p1[at++] = i;
		fill(all(SA), -1);
		rer(i, n1, 0) {
			int j = p1[SA1[i]];
			SA[--bkt[s[j]]] = j;
		}
		induceSAl(t, s, SA, K); induceSAs(t, s, SA, K);
		return SA;
	}
	vi get_sa(const vi& sin, int K = 26) {
		int n = (int)sin.size();
		vi s(n + 1, 0);
		rep(i, 0, n) s[i] = sin[i] + 1;
		bkt.resize(max(n + 1, K + 1));
		return get_sa_impl(s, K + 1);
	}
}

無理やり圧縮しまくって81行になった。一応POJ3581でverify済み。
番兵とかも気を遣わず、元の文字列を数字に変換したのをそのままget_saの引数にすればSAが得られるようにした。
namespaceよりもstructにする方がいい気もするが、今のところnamespaceで困らないのでこれで。
(9/2追記)

rer(i, n1, 0) {
	int j = p1[SA1[i]]; //int j = p1[i]としてバグらせていた。
	SA[--bkt[s[j]]] = j;
}
induceSAl(t, s, SA, K); induceSAs(t, s, SA, K);

8/31にあげたコードがバグっていたので修正。なのになぜかPOJ3581は通ったんだよなぁ…。
デバッグの際にgasin氏のSAを活用させてもらった。gasinありがとー!