Loading

Gym-102411L

L - Lengths and Periods

tag: 后缀数组,启发式合并

给定字符串 \(s\) ,设 \(w\)\(s\) 的某一子串, 若有 \(w = x^nx_0\) ,且 \(x_0\)\(x\) 的前缀, 称 \(\dfrac {|w|} {|x|}\)\(w\)\(critical\ exponent\) 。 求 \(s\) 的所有子串的最大的 \(critical\ exponent\) 。 ( \(|s| \le 2e5\) )

构造答案

首先, 对于下标 \(i < j\) ,可以构造出答案

\[ans = \dfrac {lcp(rk[i],rk[j]) + j - i} {j - i} \]

$lcp(rk[i],rk[j]) $ 表示第 i 个后缀与第 j 个后缀的 lcp

构造出的字符串就是 s.substr(i,j-i) + lcp(i,j)

求解最大值

\[lcp(x,y) = min_{i = x + 1}^y height_i \]

可以降序扫描 height 数组,扫描到下标 \(idx\) 的时候合并 \(idx\)\(idx-1\)

这样每次扫描到的都是一段连续区间的 height 最小值,就是 lcp ,所以我们需要寻找最小的 \(j - i\)

可以用集合来保存所有的后缀开始的下标,用并查集 + 启发式合并完成该步骤

/*
 * @Author: zhl
 * @LastEditTime: 2021-03-01 19:27:08
 */
#include<bits/stdc++.h>
using namespace std;

const int N = 3e5 + 10;
typedef long long ll;

int n, m;

char s[N];
int sa[N], x[N], y[N], c[N], rk[N], height[N];
/*
    sa[i] :
    x[i] : 第一关键字
    y[i] : 第二关键字
    c[i] : 桶
    rk[i] : 
*/

void get_sa(){
    /*
        根据首字母进行基数排序
    */
    for (int i = 1; i <= n; i++) c[x[i] = s[i]] ++;
    for (int i = 2; i <= m; i++) c[i] += c[i - 1];
    for (int i = n; i; i--) sa[c[x[i]] --] = i;  // s[i] = k 表示 rank i 的串从 k 位置开始

    /*
        开始倍增
    */
    for (int k = 1; k <= n; k <<= 1)
    {	
        /*
            此时已经根据前k个字母排好序,要根据2*k个字母排好序
            先按照后 k 个字母(第二关键字)排序,再根据前 k 个字母排序(稳定排序不会改变相对位置)
        */
        int num = 0;
        
        for (int i = n - k + 1; i <= n; i++) y[++num] = i; // 这个区间第二关键字是 空串
        for (int i = 1; i <= n; i++) //已经按前 k 个字母排序, 第 i 个后缀的第二关键字是 第 i + k的第一关键字
            if (sa[i] > k)
                y[++num] = sa[i] - k;
        


        for (int i = 1; i <= m; i++) c[i] = 0;
        for (int i = 1; i <= n; i++) c[x[i]] ++;
        for (int i = 2; i <= m; i++) c[i] += c[i - 1];

        // 按照第二关键字的顺序从后往前枚举
        for (int i = n; i; i--) sa[c[x[y[i]]] --] = y[i], y[i] = 0;
        swap(x, y); //把 x 暂时存到 y 中

        //离散化
        x[sa[1]] = 1, num = 1;
        for (int i = 2; i <= n; i++)
            x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++num;
        if (num == n) break;
        m = num;
    }
}

/*
    h[i] = height[rk[i]]
    h[i] >= h[i-1] - 1
    k 是当前的 h[i]
*/
void get_height()
{
    for (int i = 1; i <= n; i++) rk[sa[i]] = i;
    for (int i = 1, k = 0; i <= n; i++)
    {
        if (rk[i] == 1) continue;
        if (k) k--; //只需要从 h[i-1] - 1 开始枚举就可以
        int j = sa[rk[i] - 1];
        while (i + k <= n && j + k <= n && s[i + k] == s[j + k]) k++;
        height[rk[i]] = k;
    }
}

set<int>st[N];
int fa[N],ans[N],r[N];

void UFS_init(){
    for(int i = 1;i <= n;i++)fa[i] = i,st[i].insert(sa[i]),ans[i] = n;
}
int find(int a){
    return a == fa[a] ? a : fa[a] = find(fa[a]);
}

int merge(int a,int b){
    a = find(a);b = find(b);
    if(st[a].size() > st[b].size())swap(a,b);

    fa[a] = b;
    ans[b] = min(ans[b], ans[a]);

    for(int v : st[a]){
        auto p = st[b].lower_bound(v);
        if(p != st[b].end()) ans[b] = min(ans[b], *p - v);
        if(p != st[b].begin()) ans[b] = min(ans[b], v - *prev(p));
        st[b].insert(v);
    }
    st[a].clear();
    return ans[b];
}
int main()
{	

	scanf("%s", s + 1);
	n = strlen(s + 1), m = 122;
	get_sa();
	get_height();

	UFS_init();

    for(int i = 1;i <= n;i++) r[i] = i;
    sort(r + 1,r + 1 + n,[&](int a,int b){
        return height[a] > height[b];
    });

    int ansx = 1,ansy = 1;
    for(int i = 1;i <= n;i++){
        int idx = r[i];
        int y = merge(idx,idx - 1);
        int x = y + height[idx];
        if(1ll * x * ansy > 1ll * y * ansx){
            ansx = x;ansy = y;
        }
    }
    int g = __gcd(ansx,ansy);
    ansx /= g,ansy /= g;

    printf("%d/%d\n",ansx, ansy);
}


解法二

这道题也可以用 SAM 求解, 反串建 SAM , 两个实点的 LCA 的 len 就是原串中的 LCP

然后搞一个 dsu on Tree

posted @ 2021-03-04 19:14  —O0oO-  阅读(83)  评论(0编辑  收藏  举报