[后缀自动机][树上启发式合并] LOJ #6041. 「雅礼集训 2017 Day7」事情的相似度

21 篇文章 0 订阅
8 篇文章 0 订阅

Solution S o l u t i o n

O(nnlogn) O ( n n log ⁡ n ) 的莫队连 50000 50000 都过不了qwq。

%%%gjghfd

gjghfd教我SAM+树上启发式合并的做法。
每两个后缀的贡献会在LCA处统计到。
一个暴力的想法就是枚举LCA,那就只需要考虑子树中的点对。有实际贡献意义的就是两个编号最近的点对。
考虑树上启发式合并,用线段树维护已加入的点,每次加入一个新的点的时候只要找到左右相应的最近的点。会得到一些三元组 (x,y,deplca(x,y)) ( x , y , d e p lca ( x , y ) )
然后就是一个类似离线数点的过程。 two-pointers two-pointers 扫左端点,树状数组维护右端点就好了。

#include <bits/stdc++.h>
#define show(x) cerr << #x << " = " << x << endl
using namespace std;

typedef long long ll;
typedef pair<int, int> pairs;

const int K = 18;
const int N = 202020;
inline char get(void) {
    static char buf[100000], *S = buf, *T = buf;
    if (S == T) {
        T = (S = buf) + fread(buf, 1, 100000, stdin);
        if (S == T) return EOF;
    }
    return *S++;
}
template<typename T>
inline void read(T &x) {
    static char c; x = 0; int sgn = 0;
    for (c = get(); c < '0' || c > '9'; c = get()) if (c == '-') sgn = 1;
    for (; c >= '0' && c <= '9'; c = get()) x = x * 10 + c - '0';
    if (sgn) x = -x;
}
inline void read(char *c) {
    for (*c = get(); *c < '0' || *c > '1'; *c = get());
    for (; *c >= '0' && *c <= '1'; *c = get()) ++c;
    *c = 0;
}

namespace SAM {
    int last, tcnt, root;
    int id[N], rgt[N];
    int mx[N], par[N];
    int go[N][2];
    inline void init(void) {
        last = tcnt = root = 1;
    }
    inline int extend(int c, int i) {
        int p = last, np = ++tcnt;
        id[np] = i; rgt[np] = 1;
        mx[np] = mx[p] + 1;
        for (; !go[p][c] && p; p = par[p]) go[p][c] = np;
        if (p) {
            int q = go[p][c];
            if (mx[q] == mx[p] + 1) {
                par[np] = q;
            } else {
                int nq = ++tcnt;
                mx[nq] = mx[p] + 1;
                par[nq] = par[q];
                par[q] = par[np] = nq;
                memcpy(go[nq], go[q], sizeof go[q]);
                for (; go[p][c] == q; p = par[p])
                    go[p][c] = nq;
            }
        } else {
            par[np] = root;
        }
        return last = np;
    }
}
namespace BIT {
    int c[N];
    int maxn;
    inline void init(int n) {
        maxn = n;
    }
    inline void add(int x, int a) {
        for (; x <= maxn; x += x & -x)
            c[x] = max(c[x], a);
    }
    inline int query(int x) {
        int mx = 0;
        for (; x; x -= x & -x)
            mx = max(mx, c[x]);
        return mx;
    }
}
namespace segTree {
    int sm[N << 2];
    inline void insert(int o, int l, int r, int pos, int x) {
        sm[o] += x;
        if (l == r) return;
        int mid = (l + r) >> 1;
        if (pos <= mid) insert(o << 1, l, mid, pos, x);
        else insert(o << 1 | 1, mid + 1, r, pos, x);
    }
    inline int findL(int o, int l, int r, int pos) {
        if (sm[o] == 0 || l > pos) return -1;
        if (l == r) return l;
        int mid = (l + r) >> 1;
        if (r <= pos) {
            if (!sm[o << 1 | 1]) return findL(o << 1, l, mid, pos);
            return findL(o << 1 | 1, mid + 1, r, pos);
        }
        int res = findL(o << 1 | 1, mid + 1, r, pos);
        if (~res) return res;
        return findL(o << 1, l, mid, pos);
    }
    inline int findR(int o, int l, int r, int pos) {
        if (sm[o] == 0 || r < pos) return -1;
        if (l == r) return l;
        int mid = (l + r) >> 1;
        if (l >= pos) {
            if (!sm[o << 1]) return findR(o << 1 | 1, mid + 1, r, pos);
            return findR(o << 1, l, mid, pos);
        }
        int res = findR(o << 1, l, mid, pos);
        if (~res) return res;
        return findR(o << 1 | 1, mid + 1, r, pos);
    }
}

using SAM::id;
using segTree::insert;
using segTree::findL;
using segTree::findR;

int n, m, clc;
char s[N];
struct qry {
    int l, r, id;
    inline bool operator <(const qry &b) const {
        return l > b.l;
    }
} q[N];
int size[N], fa[N], son[N], dep[N];
int pre[N], post[N], erp[N];
int ans[N];
vector<int> G[N];
vector<pairs> p[N];

inline void addEdge(int from, int to) {
    G[from].push_back(to);
}
inline void build(void) {
    using namespace SAM;
    for (int i = 1; i <= tcnt; i++) {
        if (fa[i] = par[i])
            addEdge(par[i], i);
        size[i] = rgt[i];
        dep[i] = mx[i];
    }
    BIT::init(n);
}
inline void dfs1(int u) {
    erp[pre[u] = ++clc] = u;
    for (int to: G[u]) {
        dfs1(to); size[u] += size[to];
        if (size[son[u]] < size[to])
            son[u] = to;
    }
    post[u] = clc;
}
inline void insert(int x, int mx) {
    int L = findL(1, 1, n, x - 1);
    int R = findR(1, 1, n, x + 1);
    if (~L) p[L].push_back(pairs(x, mx));
    if (~R) p[x].push_back(pairs(R, mx));
    insert(1, 1, n, x, 1);
}
inline void dfs(int u, int iw) {
    for (int to: G[u])
        if (to != son[u]) dfs(to, 0);
    if (son[u]) dfs(son[u], 1);
    for (int to: G[u])
        if (to != son[u])
            for (int i = pre[to]; i <= post[to]; i++) {
                int v = erp[i];
                if (id[v]) insert(id[v], dep[u]);
            }
    if (id[u]) insert(id[u], dep[u]);
    if (iw == 0)
        for (int i = pre[u]; i <= post[u]; i++) {
            int v = erp[i];
            if (id[v]) insert(1, 1, n, id[v], -1);
        }
}

int main(void) {
    freopen("1.in", "r", stdin);
    freopen("1.out", "w", stdout);
    read(n); read(m);
    read(s);
    SAM::init();
    for (int i = 0; i < n; i++)
        SAM::extend(s[i] - '0', i + 1);
    build(); dfs1(1); dfs(1, 0);
    for (int i = 1; i <= m; i++) {
        read(q[i].l); read(q[i].r);
        q[i].id = i;
    }
    sort(q + 1, q + m + 1);
    int j = n;
    for (int i = 1; i <= m; i++) {
        for (; j >= q[i].l; j--)
            for (auto u: p[j])
                BIT::add(u.first, u.second);
        ans[q[i].id] = BIT::query(q[i].r);
    }
    for (int i = 1; i <= m; i++)
        printf("%d\n", ans[i]);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值