【SA+启发式合并+主席树】P7361拜神

题目链接

由于自己感觉写不明白,先来写题解

学习自 Alex_wei

题意

给定长为 \(n\leq 5\times 10^4\) 字符串,\(q\leq 10^5\) 次询问,每次询问 \([l,r]\) 子串内出现两次以上的最长子串长度。

思路

  • 长度为 \(L\) 子串出现两次以上,等价于存在 \(l \leq i \leq j \leq r - L + 1\)\(lcp(i,j)\geq L\) ,满足该条件当且仅当若将所有
    \(\geq L\)\(ht_x\) 值对应的两个位置 \(sa_{x - 1}\)\(sa_x\) 之间连边,\(i, j\) 在同一连通块,可以用并查集维护。
  • 而答案 \(L\) 显然满足二分性,题目可以在 \(O(nlogn)\) 的 check 复杂度通过。
  • 借鉴品酒大会的经验,我们从 height 值从大到小加点,同时并查集维护合并。每次 check 找 \([l, r - L + 1]\) 内有没有两个点满足条件。
  • 可以记录每个后缀在 height 中的后继节点,用数据结构来维护这些节点对应后继节点的下标值,这样每次只用查询 \([l,r-L+1]\) 的点中是否存在最小值小于等于 \(r-L+1\)
  • 而由于我们没有离线询问,在线操作,L 不规律变化,故需要对长度 \(L\) 持久化,也就是用持久化线段树维护。
  • 对于并查集合并后每个点后继节点的维护,可以用启发式合并来优雅暴力,对每个代表元开一个 set,新加入节点二分大于等于他的点 \(suf\) 和小于他的点 \(pre\)。插入后更新 \(suf\) 和 插入点即可。
  • 在单次合并后可能有多个点的 \(suf\) 值发生了改变,加入到容器中,按下标排序,从小到大依次进持久化线段树中更新。
  • 时空复杂度: \(O(nlog^2n)\)

实现细节

  • 持久化线段树不是像普通主席树的使用,考虑一下每个点的后继在更新中只会不断变小。修改原根的值不影响答案。持久化的目的是方便找出对于每个长度他所对应的所有节点下标位置。
    而不是传统的前缀信息相减的持久化。
  • 所以每次修改一系列数的时候,对于旧根没有的节点直接新建节点,已有的叶子直接修改叶子的值。
  • 查询的时候,直接找长度对应的根,只会走向大于等于 L 以前的路径,找到对应的节点。
  • 二分的时候,查询区间应该在 \([L,R-m]\) ,端点只能在这些范围。二分长度范围是 \([0, R-L]\) ,都是一些细节性的东西。

Code

#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef double db;
#define re _read
#define ALL(x) (x).begin(),(x).end()
#define SZ(v) ((int)v.size())
#define fi first
#define se second
#define pb push_back
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define endl "\n"
using namespace std;
mt19937 mrand(random_device{}()); 
int rnd(int x) { return mrand() % x;}
template <typename T> std::ostream &operator<<(std::ostream &out, const std::vector<T> &v) { out << "["; bool first = true; for (auto &&e : v) { if (first) { first = false;} else {out << ", ";} out << e; } return out << "]"; }
template <typename A, typename B> std::ostream &operator<<(std::ostream &out, const std::pair<A, B> &v) {  return out << "(" << v.first << ", " << v.second << ")"; }
template <typename K> std::ostream &operator<<(std::ostream &out, const std::set<K> &s) {  out << "{"; bool first = true; for (auto &&k : s) { if (first) { first = false; } else { out << ", "; } out << k; } return out << "}"; }
template <typename K, typename V> std::ostream &operator<<(std::ostream &out, const std::map<K, V> &m) { out << "{"; bool first = true; for (auto &&[k, v] : m) { if (first) { first = false; } else { out << ", "; } out << k << ": " << v; } return out << "}"; }
template <class T> vector<vector<T>> Vector(int n, int m) { return vector<vector<T>> (n, vector<T> (m, 0)); }
template <class T> vector<vector<vector<T>>> Vector(int i, int j, int k) { return vector<vector<vector<T>>> (i, vector<vector<T>>(j, vector<T>(k, 0))); }
template <typename T> void OUT(T* a, int l, int r) { for (int i = l; i <= r; i ++) cout << a[i] << " "; puts(""); }
template<class T>
inline void _read(T& x) {
    static T ans;
    static unsigned int c;
    static bool p;
    for (c = getchar(); c != '-' && (c < '0' || c > '9'); c = getchar());
    if (c == '-') p = false, c = getchar(); else p = true;
    for (ans = 0; c <= '9' && c >= '0'; c = getchar()) ans = ans * 10 + c - '0';
    x = p ? ans : -ans;
}
/*----------------------------------------------------------------------------------------------------*/

#define RMQ
const int maxn = 1e6 + 10;
struct SA {
    int n, sa[maxn], rk[maxn], id[maxn], cnt[maxn], height[maxn], px[maxn];
    void get_sa(const char* s, int _n) {
        n = _n;
        int m = 300, p = 0;
        for (int i = 0; i <= m; i++) cnt[i] = 0;
        for (int i = 1; i <= n; i++) cnt[rk[i] = (int)s[i]] ++;
        for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
        for (int i = n; i >= 1; i--) sa[cnt[rk[i]]--] = i;
        for (int w = 1; w <= n; w <<= 1, m = p, p = 0) {
            for (int i = n - w + 1; i <= n; i++) id[++p] = i;
            for (int i = 1; i <=n; i++)
                if (sa[i] > w) id[++p] = sa[i] - w;
            for (int i = 0; i <= m; i++) cnt[i] = 0;
            for (int i = 1; i <=n; i++) ++ cnt[rk[i]], px[i] = rk[id[i]];
            for (int i = 1; i <= m; i++) cnt[i] += cnt[i - 1];
            for (int i = n; i >= 1; i--) sa[cnt[px[i]] -- ] = id[i];
            for (int i = 1; i <= n; i++) swap(rk[i], id[i]);
            rk[sa[1]] = p = 1;
            for (int i = 2; i <= n; i++) 
                rk[sa[i]] = (id[sa[i]] == id[sa[i - 1]] && id[sa[i] + w] == id[sa[i - 1] + w] ? p : ++p);
            if (p >= n) break;
        }
    }
    void get_height(const char* s){
        for (int i = 1, k = 0; i <= n; i++) {
            if (k) -- k;
            int j = sa[rk[i] - 1];
            while (s[i + k] == s[j + k]) ++ k;
            height[rk[i]] = k;
        }
    }
} sa;

const int N = 5e4 + 10, INF = 1e9;
vector<PII> tmp;

// #define ROSHIN

// log =n * log

struct Chairman_Tree {
    int node_cnt, root[N];
    struct T {
        int l, r;
        int v;
    }tr[N << 8];
    int get_node() {
        node_cnt++;
        tr[node_cnt].l = tr[node_cnt].r = 0, tr[node_cnt].v = INF;
        return node_cnt;
    }
    void pushup(T& rt) {
        rt.v = min(tr[rt.l].v, tr[rt.r].v);
    }
    void build(int& u, int l, int r) {
        u = get_node();
        if (l == r) return ;
        int mid = (l + r) >> 1;
        build(tr[u].l, l, mid), build(tr[u].r, mid + 1, r);

    }
    void modify(int pre, int& u, int l, int r) {
        if (tmp.empty() || tmp.back().first > r) return ;
        assert(tmp.back().first >= l);
        u = get_node();     // 新建节点。
        tr[u].l = tr[pre].l, tr[u].r = tr[pre].r;
        if (l == r) {
            #ifdef ROSHIN
            printf("l=%d v=%d\n", l, tmp.back().second);
            #endif
            tr[u].v = tmp.back().second;
            tmp.pop_back();
            return ;
        }
        int mid = (l + r) >> 1;
        modify(tr[pre].l, tr[u].l, l, mid);
        modify(tr[pre].r, tr[u].r, mid + 1, r);
        pushup(tr[u]);
    }
    int query(int u, int l, int r, int ql, int qr) {
        if (!u || r < ql || l > qr || ql > qr) return INF;
        if (l >= ql && r <= qr) return tr[u].v;
        int res = INF;
        int mid = (l + r) >> 1;
        res = query(tr[u].l, l, mid, ql, qr);
        res = min(res, query(tr[u].r, mid + 1, r, ql, qr));
        #ifdef ROSHIN
        printf("l=%d, r=%d, res=%d\n", l, r, res);
        #endif
        return res;
    }
}ctr;

char s[N];
int n, q;
PII H[N];

int p[N];
set<int> S[N];

int updated[N], cnt = 0, id[N];

int find(int x) {
    if (x != p[x]) p[x] = find(p[x]);
    return p[x];
}

void update(int pos, int val) {
    if (!updated[pos]) id[cnt++] = pos;
    updated[pos] = val;
}

void Union(int x, int y) {
    x = find(x), y = find(y);
    if (SZ(S[x]) < SZ(S[y])) swap(x, y);
    p[y] = x;
    for (auto it: S[y]) {
        auto suf = S[x].lower_bound(it);
        if (suf != S[x].end()) update(it, *suf);
        if (suf != S[x].begin()) update(*prev(suf), it);
        S[x].insert(it);
    }
    S[y].clear();
}


int main() {
    // freopen("input.txt", "r", stdin);
    // freopen("output.txt", "w", stdout);
    re(n), re(q);
    scanf("%s", s + 1);
    sa.get_sa(s, n);
    sa.get_height(s);
    for (int i = 1; i <= n; i++) p[i] = i, S[i].insert(i);
    for (int i = 2; i <= n; i++) H[i - 1] = {sa.height[i], i};
    sort(H + 1, H + n);
    ctr.build(ctr.root[n], 1, n);

    for (int i = n - 1, j = n - 1; i >= 1; i--) {
        cnt = 0, tmp.clear();
        while (j && H[j].first == i) {
            int idx = H[j--].second;
            Union(sa.sa[idx], sa.sa[idx - 1]);
        }
        sort(id, id + cnt);
        for (int i = cnt - 1; i >= 0; i--) {
            tmp.pb({id[i], updated[id[i]]}), updated[id[i]] = 0;
        }
        if (tmp.empty()) 
            ctr.root[i] = ctr.root[i + 1];
        else
            ctr.modify(ctr.root[i + 1], ctr.root[i], 1, n);
        #ifdef ROSHIN
        printf("L=%d\n",i);
        for (auto t: tmp) {
            cout << t.first << " " << t.se;
        }
        cout << endl;
        #endif
    }
    while (q--) {
        int L, R;
        re(L), re(R);
        int l = 0, r = R - L;
        while (l < r) {
            int mid = (l + r + 1) >> 1;
            if (ctr.query(ctr.root[mid], 1, n, L, R - mid) <= R - mid + 1) l = mid;
            else r = mid - 1;
        }
        printf("%d\n", l);
    }
    // fclose(stdin);
    // fclose(stdout);
    return 0;
}
posted @ 2022-11-03 16:57  Roshin  阅读(51)  评论(0编辑  收藏  举报
-->