【字符串】【P5830】 【模板】失配樹

【字符串】【P5830】 【模板】失配樹

Description

給定一個長度爲 \(n\) 的字符串 \(S\),有 \(m\) 次詢問,每次詢問給定 \(S\) 的兩個前綴,求它們的最長公共 border 的長度。

最長公共 border 的含義爲,對於一個字符串 \(T\),設其 Border 集合爲所有既是 \(S\) 的前綴子串又是 \(S\) 的後綴子串的集合,兩個字符串的最長公共 border 爲兩個字符串的 Border 集合的交集中長度最長的字符串。

Limitations

\(1 \leq n \leq 10^6\)

\(1 \leq m \leq 10^5\)

Solution

注意,這篇題解不是這個模板的標準做法,也不是最簡單的做法。

兩個前綴的最長公共 border 即爲他們在 border 樹上的 LCA

因爲剛起牀就被 fa姐姐 拉來驗題,腦袋昏昏忘記了這個結論,只能再口胡一個鐵憨憨做法。

注意到所求的 border 一定既是第一個字符串的後綴,又是第二個字符串的後綴,因此一定是兩個字符串的公共後綴 ,同時注意到由於這兩個字符串的前綴是相同的,所以如果一個字符串 \(T\) 既是其中任意一個串的 border,又是兩個串的公共後綴,那麼它一定是兩個串的公共 border。並且這個條件顯然也是必要條件,因此我們在求出兩串的 lcp 以後只需要在其中任意一個串上找到其最長的長度不超過 lcp 長度的 border,那麼該串即爲兩串的最長公共 border

假設我們已經求出了兩串的 lcp 長度,那麼問題就只剩下對一個字符串求其最長的長度不超過某數的 border

我們考慮對每個前綴,將它向它的最長 border 連一條邊,那麼顯然這個圖有 \((n + 1)\) 個節點, \(n\) 條邊,又因爲這個圖是聯通的,根據樹的判定定理,這個圖是一棵樹,若規定 \(0\) 是這棵樹的根,數學歸納可得每個節點的父節點爲該節點所代表的前綴的最長 border。因爲一個節點的 border 顯然比該節點的長度小,所以任何一個節點到根所在的鏈上,若將節點按深度從小到大排列,則其所代表的前綴長度一定是單調遞增的。因此我們只需要對整棵樹進行 dfs,同時用一個棧維護當前節點到根的鏈,然後在棧裏二分即可找到所求的串。

border 的方法見 【P3375】KMP字符串匹配

而求兩個前綴的 lcp,可以對原串建立一個 SAM,兩個前綴在 parent 樹上所對應節點的 LCA 即爲他們的 lcp。也可以將原串反過來,轉化爲求兩個後綴的最長公共前綴,求出 SA 後用 height 數組解決。

但是扶蘇既不願意將原串反過來求 SA 在寫個 ST,也擔心毒瘤出題人卡了空間以後 SAM 建出來會爆空間,因此扶蘇選擇了 二分+hash 求出其 lcp

顯然公共後綴的長度滿足二分性,因此只要選擇一個滿足前綴可減性的 hash 函數就可以 \(O(1)\) check 了。

考慮時間複雜度:二分求 lcp 的複雜度是 \(O(m \log n)\),在 border 樹上二分的複雜度是 \(O(m \log n)\),因此總時間複雜度 \(O(n + m \log n)\)

Code

本來扶蘇寫了個四模數 hash,然後被卡常了就嘗試減少模數個數,最後發現單模數就可以了(霧

#include <cstdio>
#include <vector>
#include <algorithm>

const int maxh = 4;
const int maxm = 100005;
const int maxn = 1000005;

const int MOD[] = {998244353, 1000000007, 1000000009, 1145141};

int n, m, top = -1;
char S[maxn];
int border[maxn], ans[maxm], stk[maxn];
std::vector<int> son[maxn], query[maxn];

struct HASH {
  int md;
  ll hash[maxn], inv[maxn];

  ll mpow(const int a, int d, const int p) {
    ll ret = 1, tmp = a;
    while (d) {
      if (d & 1) {
        (ret *= tmp) %= p;
      }
      (tmp *= tmp) %= p;
      d >>= 1;
    }
    return ret;
  }

  void build(const int x) {
    md = x;
    ll tmp = 1, iv = mpow(100, x - 2, x);
    inv[0] = 1;
    for (int i = 1; i <= n; ++i) {
      hash[i] = (hash[i - 1] + (S[i] - 'a') * tmp) % md;
      inv[i] = inv[i - 1] * iv % md;
      (tmp *= 100) %= md;
    }
  }

  bool check(const int x, const int y, const int len) {
    ll h1 = (hash[x] - hash[x - len]) * inv[x - len] % md, h2 = (hash[y] - hash[y - len]) * inv[y - len] % md;
    if (h1 < 0) h1 += md;
    if (h2 < 0) h2 += md;
    if (h1 != h2) {
      return false;
    } else {
      return true;
    }
  }
};
HASH h[maxh];

int ReadStr(char *p);
void dfs(const int u);

int main() {
  freopen("1.in", "r", stdin);
  n = ReadStr(S);
  for (int i = 0; i < maxh; ++i) {
    h[i].build(MOD[i]);
  }
  for (int i = 2, j = 0; i <= n; ++i) {
    while (j && (S[j + 1] != S[i])) {
      j = border[j];
    }
    if (S[j + 1] == S[i]) {
      ++j;
    }
    son[border[i] = j].push_back(i);
  }
  son[0].push_back(1);
  qr(m);
  for (int p, q, Ans, i = 1; i <= m; ++i) {
    p = q = Ans = 0; qr(p); qr(q);
    for (int l = 1, r = std::min(p, q) - 1, mid = (l + r) >> 1; l <= r; mid = (l + r) >> 1) {
      bool flag = true;
      for (int i = 0; i < maxh; ++i) if ((flag = h[i].check(p, q, mid)) == false) {
        break;
      }
      if (flag) {
        l = (Ans = mid) + 1;
      } else {
        r = mid - 1;
      }
    }
    ans[i] = Ans;
    query[std::min(p, q)].push_back(i);
  }
  dfs(0);
  for (int i = 1; i <= m; ++i) {
    qw(ans[i], '\n', true);
  }
  return 0;
}

int ReadStr(char *p) {
  auto beg = p;
  do *(++p) = IPT::GetChar(); while ((*p >= 'a') && (*p <= 'z'));
  *p = 0;
  return p - beg - 1;
}

void dfs(const int u) {
  stk[++top] = u;
  for (auto v : query[u]) {
    int w = ans[v]; ans[v] = 0;
    for (int l = 1, r = top, mid = (l + r) >> 1; l <= r; mid = (l + r) >> 1) if (stk[mid] <= w) {
      ans[v] = stk[mid];
      l = mid + 1;
    } else {
      r = mid - 1;
    }
  }
  for (auto v : son[u]) {
    dfs(v);
  }
  --top;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章