字符串下标从 1 开始。
 字符串 s 的长度为 n。
 " 后缀 i" 代指以第 i 个字符开头的后缀,存储时用 i 代表字符串 s 的后缀 s[i ... n]。
后缀数组(Suffix Array)主要关系到两个数组:sa 和 rk。
 后缀数组sa,sa[i] 表示将所有后缀排序后第 i 小的后缀的编号;
 排名数组 rk, rk[i] 表示后缀 i 的排名。
 这两个数组满足性质:sa[rk[i]]=rk[sa[i]]=i。



/**
 *    倍增的过程是 O(log n),而每次倍增用 sort 对子串进行排序是 O(n\log n),而每次子串的比较花费 2 次字符比较;
  *    这个算法的时间复杂度就是 O(n\log^2n)。
  */
 // 常规解法, 倍增快速排序
 #include <iostream>
 #include <string.h>
 #include <algorithm>
 using namespace std;
const int MAXN = 1000010;
 char str[MAXN];
 int sa[MAXN];      // 后缀数组sa[i]表示将所有后缀排序后,第i小的后缀的编号
 // 为了防止访问 rk[i+k] 导致数组越界,开两倍数组, 省去越界检测,简化程序。
 int rk[MAXN << 1]; // 后缀i的排名,常称为排名数组
 int oldrk[MAXN << 1];
 int k;
 int main()
 {
     cin >> str + 1;
     int i = 0, len = strlen(str+1);
    // 初始化后缀树组与排名数组
     for (i = 1; i <= len; ++i)
     {
         sa[i] = i;
         rk[i] = str[i];
     }
     // 倍增排序
     for (k = 1; k < len; k <<= 1)
     {
         /**
          *    每个后缀子串的次序可以表示为一个二元组(x, y), x表示前半段的次序号,y表示后半段的次序号
          *    由于上一次的排序结果已知(即前半段x的排序已知),故只要对后半段进行比较就可以得到当前子串的次序
* 第n次排序表示对每个后缀子串的前1~2^(n-1)个字符进行排序
         *    比较二元组(x, x+k) 与 (y, y+k)
          *    如果第一关键字相等,则比较第二关键字,关键字小的排名更靠前
          */
         sort(sa+1, sa+len+1, [](int x, int y) { 
                 return rk[x] == rk[y] ? rk[x+k] < rk[y+k] : rk[x] < rk[y];    
             }
         );
         // 由于计算 rk 的时候, 原来的 rk 会被覆盖,要先复制一份
         memcpy(oldrk, rk, sizeof(rk));
         int num = 0; // 当前最大的次序号
         // 按照sa从小到大给后缀子串更新次序
         for (i = 1; i <= len; ++i)
         {
             // 如果与前一个二元组不相同,则产生新的次序号
             if (oldrk[sa[i]] == oldrk[sa[i-1]] && oldrk[sa[i]+k] == oldrk[sa[i-1]+k])
                 rk[sa[i]] = num;
             else
                 rk[sa[i]] = ++num;
         }
         cout << "k: " << k << endl;
         cout << "sa: ";
         for (i = 1; i <= len; ++i)
             cout << sa[i]  << " ";
         cout << endl;
         cout << "rk: ";
         for (i = 1; i <= len; ++i)
             cout << rk[i]  << " ";
         cout << endl;
     }
    for (i = 1; i <= len; ++i)
         cout << sa[i]  << " ";
     cout << endl;
    return 0;
 }
/**
  *  input:
  *    aabaaaab
  *    output:
  *    k: 1
  *    sa: 1 4 5 6 2 7 8 3
  *    rk: 1 2 4 1 1 1 2 3
  *    k: 2
  *    sa: 4 5 6 1 7 2 8 3
  *    rk: 4 6 8 1 2 3 5 7
  *    k: 4
  *    sa: 4 5 6 1 7 2 8 3
  *    rk: 4 6 8 1 2 3 5 7
  *    4 5 6 1 7 2 8 3
  */
  
  // 倍增计数排序
  /**
  *    字符串str的下标从1开始,字符串的长度为len
  *    "后缀i"代指以第i个字符开头的后缀,存储时用i代表字符串s的后缀s[i...n]
 *    后缀数组和排名数组满足: sa[rk[i]] == rk[sa[i]] == i
  *    由于计算后缀数组的过程中排序的关键字是排名,值域为 O(n),并且是一个双关键字的排序,可以使用基数排序优化至 O(n)。
  */
 #include <iostream>
 #include <string.h>
 #include <algorithm>
 using namespace std;
const int MAXN = 1000010;
 char str[MAXN];
 int sa[MAXN];      // 后缀数组sa[i]表示将所有后缀排序后,第i小的后缀的编号
 // 为了防止访问 rk[i+k] 导致数组越界,开两倍数组, 省去越界检测,简化程序。
 int rk[MAXN << 1]; // 后缀i的排名,常称为排名数组
 int oldrk[MAXN << 1];
 int oldsa[MAXN];
 int cnt[MAXN];  // 计数排序用于计数
 const int MaxKey = 127;  // the maximum value of ASCII is 127
 int main()
 {
     cin >> str + 1;
     int i = 0, len = strlen(str+1);
     // 统计str中字符的计数分布
     for (i = 1; i <= len; ++i)
         ++cnt[rk[i] = str[i]];
     // 统计关键字小于等于i的计数分布
     for (i = 1; i <= MaxKey; ++i)
         cnt[i] += cnt[i-1];
     // 升序计数排序sa
     for (i = len; i >= 1; --i)
         sa[cnt[rk[i]]--] = i;
    memcpy(oldrk+1, rk+1, sizeof(int)*len);
     int num = 0; // 当前的最大次序号
     // 第一次排序只有一个关键字
     for (i = 1; i <= len; ++i)
         if (oldrk[sa[i]] == oldrk[sa[i-1]])
             rk[sa[i]] = num;
         else
             rk[sa[i]] = ++num;
    for (int k = 1; k < len; k <<= 1)
     {
         // 对第二关键字:oldsa[i] + k 进行计数排序
         memset(cnt, 0, sizeof(cnt));
         memcpy(oldsa+1, sa+1, sizeof(int)*len);
         for (i = 1; i <= len; ++i)
             ++cnt[rk[oldsa[i]+k]];
         // 首轮排序后,cnt的最大下标不超过len
         for (i = 1; i <= len; ++i)
             cnt[i] += cnt[i-1];
         for (i = len; i >= 1; --i)
             sa[cnt[rk[oldsa[i]+k]]--] = oldsa[i];
        // 对第一关键字:oldsa[i] 进行计数排序
         memset(cnt, 0, sizeof(cnt));
         memcpy(oldsa+1, sa+1, sizeof(int)*len);
         for (i = 1; i <= len; ++i)
             ++cnt[rk[oldsa[i]]];
         // 首轮排序后,cnt的最大下标不超过len
         for (i = 1; i <= len; ++i)
             cnt[i] += cnt[i-1];
         for (i = len; i >= 1; --i)
             sa[cnt[rk[oldsa[i]]]--] = oldsa[i];
        memcpy(oldrk+1, rk+1, sizeof(int)*len);
         num = 0; // 当前的最大次序号
         for (i = 1; i <= len; ++i)
         {
             // 如果与前一个二元组不相同,则产生新的次序号
             if (oldrk[sa[i]] == oldrk[sa[i-1]] && oldrk[sa[i]+k] == oldrk[sa[i-1]+k])
                 rk[sa[i]] = num;
             else
                 rk[sa[i]] = ++num;
         }
     }
    for (i = 1; i <= len; ++i)
         cout << sa[i]  << " ";
     cout << endl;
    return 0;
 }
/**
  *    input:
  *    aabaaaab
  *    output:
  *    4 5 6 1 7 2 8 3
  */
  
  // 针对大数据做性能优化
  /**
  *    https://www.luogu.com.cn/problem/P3809
  *    字符串str的下标从1开始,字符串的长度为len
  *    "后缀i"代指以第i个字符开头的后缀,存储时用i代表字符串s的后缀s[i...n]
 *    后缀数组和排名数组满足: sa[rk[i]] == rk[sa[i]] == i
  *    由于计算后缀数组的过程中排序的关键字是排名,值域为 O(n),并且是一个双关键字的排序,可以使用基数排序优化至 O(n)。
  *    针对大数据进行优化
  */
 #include <iostream>
 #include <string.h>
 #include <algorithm>
 using namespace std;
const int MAXN = 1000010;
 char str[MAXN];
 int sa[MAXN];      // 后缀数组sa[i]表示将所有后缀排序后,第i小的后缀的编号
 // 为了防止访问 rk[i+k] 导致数组越界,开两倍数组, 省去越界检测,简化程序。
 int rk[MAXN]; // 后缀i的排名,常称为排名数组
 int oldrk[MAXN << 1];
 int key1[MAXN]; //    key1[i] = rk[oldsa[i]](作为基数排序的第一关键字数组)
 int oldsa[MAXN];
 int cnt[MAXN];  // 计数排序用于计数
/**
  *    用函数 cmp 来计算是否重复
  *    同样是减少不连续内存访问,在数据范围较大时效果比较明显。
  */
 bool cmp(int x, int y, int k) 
 {
     return oldrk[x] == oldrk[y] && oldrk[x + k] == oldrk[y + k];
 }
int main()
 {
     cin >> str + 1;
     int i = 0, len = strlen(str+1);
     int MaxKey = 127;  // the maximum value of ASCII is 127
     // 统计str中字符的计数分布
     for (i = 1; i <= len; ++i)
         ++cnt[rk[i] = str[i]];
     // 统计关键字小于等于i的计数分布
     for (i = 1; i <= MaxKey; ++i)
         cnt[i] += cnt[i-1];
     // 升序计数排序sa
     for (i = len; i >= 1; --i)
         sa[cnt[rk[i]]--] = i;
int num = 0; // 最大次序号
    /**
      *    m=num 就是优化计数排序值域
      *    每次对 rk 进行更新之后,我们都计算了一个 num,这个 num 即是 rk 的值域,将值域改成它即可。
      */
     for (int k = 1 ;; k <<= 1, MaxKey = num)
     {
         /**
          *    第二关键字无需计数排序, 第二关键字排序的实质,
          *    其实就是把超出字符串范围(即 sa[i] + k > len)的 sa[i] 放到 sa 数组头部,然后把剩下的依原顺序放入
          */
         for (num = 0, i = len; i > len - k; --i)
             oldsa[++num] = i;
         for (i = 1; i <= len; ++i)
             if (sa[i] > k)
                 oldsa[++num] = sa[i] - k;
        /**
          *    对第一关键字:oldsa[i] 进行计数排序
          *    将 rk[oldsa[i]] 存下来,减少不连续内存访问, 这个优化在数据范围较大时效果非常明显。
          */
         memset(cnt, 0, sizeof(cnt));
         for (i = 1; i <= len; ++i)
             ++cnt[key1[i] = rk[oldsa[i]]];
         for (i = 1; i <= MaxKey; ++i)
             cnt[i] += cnt[i-1];
         for (i = len; i >= 1; --i)
             sa[cnt[key1[i]]--] = oldsa[i];
        memcpy(oldrk+1, rk+1, sizeof(int)*len);
         num = 0; // 当前的最大次序号
         for (i = 1; i <= len; ++i)
         {
             // 如果与前一个二元组不相同,则产生新的次序号
             rk[sa[i]] = cmp(sa[i], sa[i - 1], k) ? num : ++num;
         }
         // 若其值域为 [1,n] 那么每个排名都不同,此时无需再排序
         if (num == len)
             break;
     }
    for (i = 1; i <= len; ++i)
         cout << sa[i]  << " ";
     cout << endl;
    return 0;
 }
/**
  *    input:
  *    aabaaaab
  *    output:
  *    4 5 6 1 7 2 8 3
  */