1. 程式人生 > 其它 >[題解]CF1073G Yet Another LCP Problem

[題解]CF1073G Yet Another LCP Problem

CF1073G Yet Another LCP Problem 題解

這題從早上調到下午,伴隨著 \(3\) 杯茶的離去,SA、線段樹、st 表各掛了 \(2\) 次/kk

#1.0 題意簡述

Time Limit: 2s | Memory Limit: 256MB

\(lcp(i,j)\) 表示 \(i\) 這個字尾和 \(j\) 這個字尾的最長公共字首長度,

給定一個字串 \(S(|S|\leq2\cdot10^5)\)\(q(q\leq2\cdot10^5)\) 次詢問,每次詢問的時候給出兩個正整數集合 \(A\)\(B\),求 \(\sum_{i \in A,j \in B}lcp(i,j)\) 的值

保證資料滿足 \(\sum|A|,\sum|B|\leq2\cdot 10^5\)

.

#2.0 大體思路

求字尾之間的 LCP 的和,很容易(並不)想到使用字尾陣列來解決問題,於是我們先求出字尾陣列及對應的 \(height\) 陣列。

這裡需要提前說明一個將要使用的定理:

\[LCP(i,j)=\min_{i+1\leq k\leq j}\{height_k\} \]

於是可以用 st 表 \(O(n\log n)\) 預處理,\(O(1)\) 查詢。

#2.1 分塊

考慮對於集合 \(B\) 中的每一個元素向集合 \(A\) 統計答案,於是我們把 \(A\)\(rank\) 排序後,分成 \(\sqrt n\) 大小的塊,然後再考慮如何對於 \(b_i\) 計算對於 \(A\)

的所有答案。

於是我們對於 \(b_i\) 將所有情況分為三種,分別討論:

  1. 當前塊左端點的 \(rank\leq rank_{b_i}\leq\) 當前塊的右端點的 \(rank\),如下圖

    這種情況暴力掃一遍統計答案即可。

  2. \(rand_{b_i}<\) 當前塊左端點,如下圖

    根據上面給出的定理,我們可以很容易得到 \(b_i\) 與這一段的 LCP 一定是(非嚴格)遞減的, 於是當我們求出 \(b_i\) 與左端點的 LCP 為 \(x\),然後鵝分最後一個 LCP\(\geq x\) 的位置,然後顯然這個位置及之前的所有的貢獻都是 \(x\),剩下的部分可以提前維護出來做字尾和得到。

  3. 當前塊右端點 \(<rand_{b_i}\),與上種情況基本一致。

於是時間複雜度為 \(O(n\sqrt n\log\sqrt n)\),很遺憾不能通過本題

#2.2 線段樹

我們嘗試對以上分塊的做法進行一些優化。上面的方法之所以要分塊,是因為要統計 LCP 的字首和。那麼我們嘗試動態維護這個東西。

不妨把 \(B\) 中的元素也\(rank\) 排序。對於 \(B\) 排序後的第 \(i\) 個元素,設它與 \(A\)\(rank\) 小於等於它的元素 \(k\) 的 LCP 為 \(d_{i,j}\)\(j\) 表示 \(k\)\(rank\) 排序在 \(A\) 中排第 \(j\) 名)。那麼,根據上文提到的定理,從 \(d_i\) 轉移到 \(d_{i+1}\) 時,只需要把所 \(d_i\) 中所有大於 \(LCP(b_i,b_{i+1})\) 的都改成 \(LCP(b_i,b_{i+1})\),再將 \(A\) 中所有滿足 \(rank_{b_i}<rank_{a_k}\leq rank_{b_{i+1}}\) 的元素加入 \(d_{i+1}\) 即可;

我們發現以上維護 \(d_i\) 的過程,需要支援兩個操作:

  1. 把所有大於 \(k\) 的數改為 \(k\)
  2. 單點修改一個元素。

容易發現,\(d_i\) 其實是一個單調的陣列,也就是說,所有大於 \(k\) 的數都會連在一起。也就是說,可以將其轉化為區間修改操作;

\(b_i\) 的答案,就是 \(d_i\) 的和。因此,還需要支援一個區間求和的操作;

綜上,我們發現可以用線段樹維護整個 \(d_i\) 轉移的操作即可。

以上過程統計了 \(b_i\)\(a_j(rank_{a_k}\leq rank_{b_i})\) 的答案,再反向做一遍,即可類似地統計 \(b_i\)\(a_j(rank_{a_k}>rank_{b_i})\) 的答案。這兩部分的答案相加,就是最終的答案。

時間複雜度為 \(O(n\log n)\).

#3.0 Code

#define ll long long

const int N = 400010;
const int INF = 0x3fffffff;

template <typename T> inline void read(T &x) {
    x = 0; int f = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
    for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
    x *= f;
}

template <typename T> inline T Max(T a, T b) {return a > b ? a : b;}
template <typename T> inline T Min(T a, T b) {return a < b ? a : b;}

struct Node {
    int ls, rs; ll sum; int mn, mx, cov;

    inline Node() {ls = rs = sum = mn = mx = 0, cov = -1;}
    inline void reset() {ls = rs = sum = mn = mx = 0, cov = -1;}
};

struct SegmentTree {
    Node p[N << 1]; int rt, cnt;
    
    inline SegmentTree() {rt = cnt = 0;}
    inline void reset() {while (cnt) p[cnt].reset(), -- cnt; rt = 0;}
    
    inline void cover(int k, int l, int r, int x) {
        p[k].cov = x; p[k].mn = p[k].mx = x;
        p[k].sum = 1ll * (r - l + 1) * x;
    }
    
    inline void pushup(int k) {
        int ls = p[k].ls, rs = p[k].rs;
        p[k].sum = p[ls].sum + p[rs].sum;
        p[k].mn = Min(p[ls].mn, p[rs].mn);
        p[k].mx = Max(p[ls].mx, p[rs].mx);
    }

    inline void pushdown(int k, int l, int r) {
        int ls = p[k].ls, rs = p[k].rs;
        if (~p[k].cov) {
            int mid = l + r >> 1;
            if (ls) cover(ls, l, mid, p[k].cov);
            if (rs) cover(rs, mid + 1, r, p[k].cov);
            p[k].cov = -1;
        }
    }

    void build(int &k, int l, int r) {
        if (!k) k = ++ cnt; if (l == r) return; int mid = l + r >> 1;
        build(p[k].ls, l, mid); build(p[k].rs, mid + 1, r);
    }

    void modify(int k, int l, int r, int x, int c) {
        if (l == r) {cover(k, l, r, c); return;}
        int mid = l + r >> 1; pushdown(k, l, r);
        if (x <= mid) modify(p[k].ls, l, mid, x, c);
        else modify(p[k].rs, mid + 1, r, x, c);
        pushup(k);
    }

    void recover(int k, int l, int r, int x) {
        if (p[k].mx < x) return; if (p[k].mn >= x) {cover(k, l, r, x); return;}
        int mid = l + r >> 1; pushdown(k, l, r);
        recover(p[k].ls, l, mid, x); recover(p[k].rs, mid + 1, r, x); pushup(k);
    }
} seg;

char s[N];
int sa[N], rk[N], oldrk[N], px[N], id[N], cnt[N], n;

bool comp(int x, int y, int w) {
  return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w];
}

inline void build_sa(int m) {
    int i, p, w;
    for (i = 1; i <= n; ++ i) ++ cnt[rk[i] = s[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 <<= 1, 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]] = comp(sa[i], sa[i - 1], w) ? p : ++ p;
        if (p == n) {for (int i = 1; i <= n; ++ i) sa[rk[i]] = i; break;}
    }
}
int ht[N], h[30][N], lg[N];

inline void get_rk_height() {
    for (int i = 1, k = 0; i <= n; ++ i) {
        if (rk[i] == 1) {ht[rk[i]] = k = 0; continue;}
        if (k) k --; assert(rk[i]);
        while (s[i + k] == s[sa[rk[i] - 1] + k]) ++ k;
        ht[rk[i]] = k;
    }
    for (int i = 1; i <= n; ++ i) lg[i] = lg[i >> 1] + 1;
    for (int i = 1; i <= n; ++ i) h[0][i] = ht[i];
    for (int i = 1; i <= lg[n]; ++ i) 
      for (int j = 1; j + (1 << i) - 1 <= n; ++ j)
        h[i][j] = Min(h[i - 1][j], h[i - 1][j + (1 << i - 1)]);
}

int q, a[N], b[N];

inline bool cmp(int x, int y) {return rk[x] < rk[y];}

inline int query(int l, int r) {
    int k = lg[r - l + 1];
    return Min(h[k][l], h[k][r - (1 << k) + 1]);
}

inline int lcp(int x, int y) {
    if (x == y) return n - x + 1;
    if (rk[x] > rk[y]) swap(x, y);
    return query(rk[x] + 1, rk[y]);
}

int main() {
    read(n); read(q); scanf("%s", s + 1); lg[0] = -1;
    build_sa(256); get_rk_height();
    while (q --) {
        int k, l; ll ans  = 0; read(k), read(l);
        for (int i = 1; i <= k; ++ i) read(a[i]);
        for (int i = 1; i <= l; ++ i) read(b[i]);
        sort(a + 1, a + k + 1, cmp);
        sort(b + 1, b + l + 1, cmp);
        seg.reset(); seg.build(seg.rt, 1, k);
        for (int i = 1, j = 1; i <= l; ++ i) {
            seg.recover(seg.rt, 1, k, lcp(b[i], b[i - 1])); 
            while (j <= k && rk[a[j]] <= rk[b[i]])
              seg.modify(seg.rt, 1, k, j, lcp(b[i], a[j])), ++ j;
            ans += seg.p[seg.rt].sum;
        }
        seg.reset(); seg.build(seg.rt, 1, k);
        for (int i = l, j = k; i >= 1; -- i) {
            seg.recover(seg.rt, 1, k, lcp(b[i], b[i + 1]));
            while (j >= 1 && rk[a[j]] > rk[b[i]])
              seg.modify(seg.rt, 1, k, j, lcp(b[i], a[j])), j --;
            ans += seg.p[seg.rt].sum;
        }
        printf("%lld\n", ans);
    }
    return 0;
}