1 条题解

  • 0
    @ 2026-1-15 8:49:34

    暴力做法就是每次最坏 𝑂(𝑛) O(n) 地判断当前应该取首还是尾(即比较取首得到的字符串与取尾得到的反串的大小),只需优化这一判断过程即可.

    由于需要在原串后缀与反串后缀构成的集合内比较大小,可以将反串拼接在原串后,并在中间加上一个没出现过的字符(如 #,代码中可以直接使用空字符),求后缀数组,即可 𝑂(1) O(1) 完成这一判断.

    #include <cctype>
    #include <cstring>
    #include <iostream>
    using namespace std;
    
    constexpr int N = 1000010;
    
    char s[N];
    int n, sa[N], id[N], oldrk[N * 2], rk[N * 2], px[N], cnt[N];
    
    bool cmp(int x, int y, int w) {
      return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w];
    }
    
    int main() {
      int i, w, m = 200, p, l = 1, r, tot = 0;
    
      cin >> n;
      r = n;
    
      for (i = 1; i <= n; ++i)
        while (cin >> s[i], !isalpha(s[i]));
      for (i = 1; i <= n; ++i)
        rk[i] = rk[2 * n + 2 - i] = s[i];  // 拼接正反两个字符串,中间空出一个字符
    
      n = 2 * n + 1;
      // 求后缀数组
      for (i = 1; i <= n; ++i) ++cnt[rk[i]];
      for (i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
      for (i = n; i >= 1; --i) sa[cnt[rk[i]]--] = i;
    
      for (w = 1; w < n; w *= 2, m = p) {  // m=p 就是优化计数排序值域
        for (p = 0, i = n; i > n - w; --i) id[++p] = i;
        for (i = 1; i <= n; ++i)
          if (sa[i] > w) id[++p] = sa[i] - w;
        memset(cnt, 0, sizeof(cnt));
        for (i = 1; i <= n; ++i) ++cnt[px[i] = rk[id[i]]];
        for (i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
        for (i = n; i >= 1; --i) sa[cnt[px[i]]--] = id[i];
        memcpy(oldrk, rk, sizeof(rk));
        for (p = 0, i = 1; i <= n; ++i)
          rk[sa[i]] = cmp(sa[i], sa[i - 1], w) ? p : ++p;
      }
      // 利用后缀数组O(1)进行判断
      while (l <= r) {
        cout << (rk[l] < rk[n + 1 - r] ? s[l++] : s[r--]);
        if ((++tot) % 80 == 0) cout << '\n';  // 回车
      }
    
      return 0;
    }
    
    • 1

    信息

    ID
    1433
    时间
    1000ms
    内存
    256MiB
    难度
    6
    标签
    递交数
    71
    已通过
    23
    上传者