• 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
    ",ansx, ansy);
    }
    
    
    

    解法二

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

    然后搞一个 dsu on Tree

  • 相关阅读:
    深入理解Kafka-学习笔记04-部分生产者客户端参数
    深入理解Kafka-学习笔记03-消息有序性
    IDEA包不存在找不到包解决方案
    深入理解Kafka-学习笔记02-生产者整体架构
    深入理解Kafka-学习笔记01-初识Kafka
    ClickHouse与MySQL数据类型对应
    MySQL查看版本
    从apache phoenix表提取数据为CSV
    Kafka查看版本
    HBASE查看版本
  • 原文地址:https://www.cnblogs.com/sduwh/p/14482451.html
Copyright © 2020-2023  润新知