題解【bzoj4650 [NOI2016]優秀的拆分】


Description

求對每一個連續字串將它切割成形如 AABB 的形式的方案數之和

Solution

顯然 AABB 是由兩個 AA 串拼起來的

考慮維護兩個數組 a[i] 和 b[i] ,其中 a[i] 表示以 \(i\) 結尾有多少個 AA 串,b[i] 表示以 \(i\) 開頭有多少個 AA 串

最后答案就是 \(\sum \limits _{i=1}^{n-1}a[i]b[i+1]\) (就是兩個串拼起來)

如何求 a[i] 和 b[i] 呢?

首先有一個非常顯然的 n^2 哈希做法(對於每一個 \(i\)\(j\) 掃一遍用哈希判斷有幾個 AA 串),有 95 分!

如何拿到最后的 5 分呢?考慮枚舉一個 Len ,然后對於每個點求出他是否是一個 2 * Len 的 AA 串的開頭 / 結尾。

我們每隔 Len 放一個點,這樣每一個 長度為 2 * Len 的 AA 串都至少會經過兩個相鄰的點。

所以再轉換為每兩個相鄰的點會對 a, b 產生多少貢獻。

先求出這對相鄰點所代表的前綴的最長公共后綴 LCS 和 所代表的后綴的最長公共前綴 LCP

如果 LCP + LCS < Len 就下面這種情況:

其中兩個紅線是關鍵點(相距為 Len),藍線是LCS,綠線是LCP,LCP+LCS < Len

則有

這條紫線就是第一個可能滿足條件的 AA 串

但此時我們會發現下圖

其中兩個紅色熒光筆的部分在 AA 串中是對應的,但他們至少有一個位置並不相同 (不然LCP可以再長)

所以此時不會有任意一個長度為 2 * Len 的 AA 串滿足條件。

如果 LCP + LCS >= Len 就有下面這種情況

此時中間必然就沒有空隙。可以發現:

粉色的是第一個 AA 串,可以發現它是可以分成兩個相同的 A 串的(可以理解成中間沒有縫隙了所以就沒有不一樣的了)

然后這個 AA 串可以一直往后滑動,每滑動一個位置都可以形成一個新的 AA 串知道 AA 串的后端點滑動到最右邊的綠色端點。也就是滑動到棕色 AA 串

此時可以發現,每一個存在於紅色熒光部分的點都可以作為一個新的 AA 串的開頭

同理,每一個再綠色熒光筆的點可以作為一個新的 AA 串的結尾。

於是就將紅色熒光筆的區間的 b 加上 1,綠色的 a 加上 1,就大功告成。

如何實現這個過程呢?復雜度是什么呢?

  1. 枚舉 Len ,每隔 Len 設置關鍵點:這個的復雜度是調和級數 \(O(n \log n)\)
  2. 求 后綴LCP,前綴LCS:使用后綴數組 + st 表 做到 O(1) 查詢
  3. 區間加上 1 : 差分維護就可以了。

至此,此題完結

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1001000;
int T; 
ll a[N], b[N]; 
struct SuffixArray {
  char S[N]; int n; 
  int cnt[N], sa[N], rk[N], height[N]; 
  int st[N][25], lg2[N];
  struct node {
    int id, x, y; 
  }aa[N], bb[N];
  inline void buildsa() {
    n = strlen(S + 1);
    memset(cnt, 0, sizeof(cnt)); 
    memset(height, 0, sizeof(height));
    memset(sa, 0, sizeof(sa));
    memset(rk, 0, sizeof(rk)); 
    for(int i = 1; i <= n; i++) aa[i].id = bb[i].id = aa[i].x = aa[i].y = bb[i].x = bb[i].y = 0; 
    for(int i = 1; i <= n; i++) cnt[S[i]] = 1; 
    for(int i = 1; i <= 256; i++) cnt[i] += cnt[i - 1];
    for(int i = 1; i <= n; i++) rk[i] = cnt[S[i]];
    for(int L = 1; L < n; L *= 2) {
      for(int i = 1; i <= n; i++) aa[i].id = i, aa[i].x = rk[i], aa[i].y = rk[i + L]; 
      for(int i = 1; i <= n; i++) cnt[i] = 0;
      for(int i = 1; i <= n; i++) cnt[aa[i].y]++;
      for(int i = 1; i <= n; i++) cnt[i] += cnt[i - 1];
      for(int i = n; i >= 1; i--) bb[cnt[aa[i].y]--] = aa[i];
      for(int i = 1; i <= n; i++) cnt[i] = 0;
      for(int i = 1; i <= n; i++) cnt[aa[i].x]++;
      for(int i = 1; i <= n; i++) cnt[i] += cnt[i - 1];
      for(int i = n; i >= 1; i--) aa[cnt[bb[i].x]--] = bb[i];
      for(int i = 1; i <= n; i++) 
        if(aa[i].x == aa[i - 1].x && aa[i].y == aa[i - 1].y)
          rk[aa[i].id] = rk[aa[i - 1].id];
        else rk[aa[i].id] = rk[aa[i - 1].id] + 1; 
    } for(int i = 1; i <= n; i++) sa[rk[i]] = i; int k = 0; 
    for(int i = 1; i <= n; i++) {
      if(k) k--;
      int j = sa[rk[i] - 1];
      while(i + k <= n && j + k <= n && S[i + k] == S[j + k]) k++;
      height[rk[i]] = k;
    }
  }
  inline void buildst() {
    lg2[0] = -1; for(int i = 1; i < N; i++) lg2[i] = lg2[i / 2] + 1; lg2[0] = 0; 
    for(int i = 1; i <= n; i++) st[i][0] = height[i]; 
    for(int j = 1; (1 << j) <= n; j++)
      for(int i = 1; i + (1 << j) - 1 <= n; i++)
        st[i][j] = min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
  }
  inline int Lcp(int l, int r) {
    l = rk[l], r = rk[r];
    if(l > r) swap(l, r); l++; 
    int k = lg2[r - l + 1]; 
    return min(st[l][k], st[r - (1 << k) + 1][k]); 
  }
}SA[2]; 
int main() {
  scanf("%d", &T);
  while(T--) {
    scanf("%s", SA[0].S + 1);
    int n = strlen(SA[0].S + 1);
    for(int i = 1; i <= n; i++) a[i] = b[i] = 0; 
    for(int i = 1; i <= n; i++)
      SA[1].S[i] = SA[0].S[n - i + 1];
    SA[0].buildsa(), SA[1].buildsa(); 
    SA[0].buildst(), SA[1].buildst();
    for(int Len = 1; Len <= n / 2; Len++) {
      for(int i = Len; i <= n; i += Len) {
        int l = i, r = i + Len; 
        int L = n - (r - 1) + 1, R = n - (l - 1) + 1;
        int lcp = SA[0].Lcp(l, r); lcp = min(lcp, Len);
        int lcs = SA[1].Lcp(L, R); lcs = min(lcs, Len - 1);
        if(lcp + lcs >= Len) {
          b[i - lcs]++, b[i - lcs + (lcp + lcs - Len + 1)]--;
          a[r + lcp - (lcp + lcs - Len + 1)]++, a[r + lcp]--; 
        }
      }
    } for(int i = 1; i <= n; i++) a[i] += a[i - 1], b[i] += b[i - 1]; 
    ll ans = 0; for(int i = 1; i < n; i++) ans += a[i] * b[i + 1]; 
    printf("%lld\n", ans); 
  }
  return 0; 
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM