「LOJ6198」谢特-SA+可持久化Trie+启发式分裂

Description

定义这个字符串以第 i i 个字符开头的后缀为后缀i(编号从 1 11 开始),每个后缀 i i 都有一个权值wi,同时定义两个后缀 i,j i , j (ij) ( i ≠ j ) 的贡献为它们的最长公共前缀长度加上它们权值的异或和,也就是 LCP(i,j)+(wixorwj) L C P ( i , j ) + ( w i x o r w j ) 。而你的任务就是,求出这个字符串的所有后缀两两之间贡献的最大值。

Solution

首先进行后缀排序,

考虑分治,对于区间 [L,R] [ L , R ] ,找到其 height h e i g h t 最小值的位置 minpos m i n p o s ,那么对于横跨 minpos m i n p o s 的位置,他们的 LCP L C P 已经确定了。

所以可以枚举 [L,minpos] [ L , m i n p o s ] (minpos,R] ( m i n p o s , R ] 中较小的一段中的数,然后在可持久化Trie中查询异或最大值。这样处理的复杂度为 O(minsizelog2maxsize) O ( m i n s i z e l o g 2 m a x s i z e )

总时间复杂度相当于逆向的启发式合并,为 O(nlog2n) O ( n l o g 2 n )

#include <bits/stdc++.h>
using namespace std;

const int maxn = 100005;

int n, ans, w[maxn];
char s[maxn];

inline int gi()
{
    char c = getchar();
    while (c < '0' || c > '9') c = getchar();
    int sum = 0;
    while ('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar();
    return sum;
}

namespace sa
{

    #define cmp(a, b) (y[a] == y[b] && y[a + j] == y[b + j])
    int m = 26, p, x[maxn], y[maxn], c[maxn], sa[maxn], rank[maxn];
    void build_sa()
    {
        for (int i = 1; i <= n; ++i) ++c[x[i] = s[i] - 'a' + 1];
        for (int i = 2; i <= m; ++i) c[i] += c[i - 1];
        for (int i = 1; i <= n; ++i) sa[c[x[i]]--] = i;
        for (int j = 1; j <= n; j <<= 1) {
            p = 0;
            for (int i = n; i >= n - j + 1; --i) y[++p] = i;
            for (int i = 1; i <= n; ++i)
                if (sa[i] > j) y[++p] = sa[i] - j;
            for (int i = 1; i <= m; ++i) c[i] = 0;
            for (int i = 1; i <= n; ++i) ++c[x[y[i]]];
            for (int i = 2; i <= m; ++i) c[i] += c[i - 1];
            for (int i = n; i >= 1; --i) sa[c[x[y[i]]]--] = y[i];
            swap(x, y);
            x[sa[1]] = p = 1;
            for (int i = 2; i <= n; ++i)
                x[sa[i]] = cmp(sa[i - 1], sa[i]) ? x[sa[i - 1]] : ++p;
            if (p >= n) break;
            m = p;
        }
        for (int i = 1; i <= n; ++i) rank[sa[i]] = i;
    }

    int ht[maxn];
    void build_ht()
    {
        for (int i = 1, k = 0; i <= n; ++i) {
            if (k) --k;
            if (rank[i] == n) continue;
            while (s[i + k] == s[sa[rank[i] + 1] + k]) ++k;
            ht[i] = k;
        }
    }

    int Log[maxn], Min[20][maxn], pos[20][maxn];
    int getmin(int l, int r)
    {
        --r;
        int k = Log[r - l + 1];
        if (Min[k][l] < Min[k][r - (1 << k) + 1]) return pos[k][l];
        else return pos[k][r - (1 << k) + 1];
    }

    void build_st()
    {
        for (int i = 2; i < n; ++i) Log[i] = Log[i >> 1] + 1;
        for (int i = 1; i < n; ++i) Min[0][i] = ht[sa[i]], pos[0][i] = i;
        for (int j = 0; (1 << j) < (n - 1); ++j)
            for (int i = 1; i + (1 << (j + 1)) - 1 < n; ++i) {
                if (Min[j][i] < Min[j][i + (1 << j)]) Min[j + 1][i] = Min[j][i], pos[j + 1][i] = pos[j][i];
                else Min[j + 1][i] = Min[j][i + (1 << j)], pos[j + 1][i] = pos[j][i + (1 << j)];
            }
    }

}

namespace trie
{

    int root[maxn], ch[maxn * 20][2], sum[maxn * 20], tot;

    int insert(int s1, int v)
    {
        int s2 = ++tot, tmp = s2;
        for (int i = 16, t; i >= 0; --i) {
            ch[s2][0] = ch[s1][0]; ch[s2][1] = ch[s1][1]; sum[s2] = sum[s1] + 1;
            t = (v >> i) & 1;
            s1 = ch[s1][t];
            s2 = ch[s2][t] = ++tot;
        }
        sum[s2] = sum[s1] + 1; return tmp;
    }

    int query(int s1, int s2, int v)
    {
        int ans = 0;
        for (int i = 16, t; i >= 0; --i) {
            t = ((v >> i) & 1) ^ 1;
            if (sum[ch[s2][t]] - sum[ch[s1][t]]) ans += 1 << i;
            else t ^= 1;
            s1 = ch[s1][t]; s2 = ch[s2][t];
        }
        return ans;
    }

}

void solve(int l, int r)
{
    if (l >= r) return ;
    int minpos = sa::getmin(l, r), x, y, L, R;
    if (minpos - l + 1 < r - minpos) x = l, y = minpos, L = minpos + 1, R = r;
    else x = minpos + 1, y = r, L = l, R = minpos;
    int Max = 0;
    for (int i = x; i <= y; ++i) Max = max(Max, trie::query(trie::root[L - 1], trie::root[R], w[sa::sa[i]]));
    ans = max(ans, Max + sa::ht[sa::sa[minpos]]);
    solve(l, minpos);
    solve(minpos + 1, r);
}

int main()
{
    n = gi();
    scanf("%s", s + 1);

    sa::build_sa();
    sa::build_ht();
    sa::build_st();

    for (int i = 1; i <= n; ++i) w[i] = gi();
    for (int i = 1; i <= n; ++i)
        trie::root[i] = trie::insert(trie::root[i - 1], w[sa::sa[i]]);

    solve(1, n);

    printf("%d\n", ans);

    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值