• Luogu5334 [JSOI2019] 节日庆典 【ExKMP】


    题目描述:给定一个长为 (n) 的字符串 (S),求它所有前缀的循环移位最小表示法的开头位置,相同的输出靠前的一个。

    数据范围:(nle 3 imes 10^6)


    好像无论怎么想都跟朴素暴力一样是 (O(n^2)) 的...于是官方题解就开始分析性质...

    我们考虑 (k=1 ightarrow n) 计算答案,并且只保留一些在将来有可能成为答案的点,其他的直接扔掉。我们称这些留下的点为候选点

    性质1:对于 (i<j),若 ( ext{lcp}(S[i:k],S[j:k])le k-j),则 (i,j) 中至少有一个不为候选点。

    这个很显然,就是 (i,j)(k) 之前就已经比较出了大小,那么一定会淘汰一个。

    性质2:对于 (i<j),若 ( ext{lcp}(S[i:k],S[j:k])>k-j),且 (k-jge j-i),则 (j) 不为候选点。

    这个就需要考虑一下了,首先我们设 (S=S_1S_1S_2),其中 (S_1,S_2)(S) 的两个子串。

    则对于 (S) 的三个循环移位:(S_1S_1S_2,S_1S_2S_1,S_2S_1S_1),那么 (S_1S_2S_1) 一定比其他两个之一小,也就是说不可能为候选点。

    那么如果有 (k-jge j-i),所以 (S[1:k]=ABBC),则设 (S_1=B,S_2=CA),也就是 (BCAB) 不可能成为答案,所以 (j) 不为候选点。

    这样就可以得出任意两个候选点 (i,j)(2(k-j)<k-i),所以用这两个性质计算出的候选点只有 (O(log n)) 个。

    然而为了方便计算还需要知道一点。

    性质3:对于两个候选点 (i<j)(S[j:k])(S[i:k]) 的前缀。

    这个可以由性质1直接得出。

    所以在比较候选点之间的大小的时候,不需要比较开始的一部分,剩下的可以转化为求两段后缀与整串的 lcp,这个就是 ExKMP 的 nxt 数组。

    时间复杂度 (O(nlog n)),空间复杂度 (O(n))

    #include<bits/stdc++.h>
    #define Rint register int
    using namespace std;
    const int N = 3000003;
    int n, now, p0, nxt[N], ans;
    char str[N];
    vector<int> cur, tmp;
    inline int cmp(int p, int len){
        return nxt[p] >= len ? 0 : (str[nxt[p]] < str[p + nxt[p]] ? 1 : -1);
    }
    inline int cmp(int x, int y, int len){
        int res;
        if(res = cmp(len - x + y, x - y)) return res > 0 ? x : y;
        if(res = cmp(x - y, y)) return res > 0 ? y : x;
        return y;
    }
    int main(){
        scanf("%s", str); n = strlen(str);
        nxt[0] = n; now = 0;
        while(str[now] == str[now + 1] && now + 1 < n) ++ now;
        nxt[1] = now; p0 = 1;
        for(Rint i = 2;i < n;++ i){
            if(i + nxt[i - p0] < p0 + nxt[p0]) nxt[i] = nxt[i - p0];
            else {
                now = max(0, p0 + nxt[p0] - i);
                while(str[now] == str[now + i] && now + i < n) ++ now;
                nxt[i] = now; p0 = i;
            }
        }
        for(Rint k = 0;k < n;++ k){
            tmp.resize(1); tmp[0] = k;
            for(Rint i : cur){
                while(!tmp.empty() && str[i + k - tmp.back()] < str[k]) tmp.pop_back();
                if(tmp.empty() || str[i + k - tmp.back()] == str[k]){
                    while(!tmp.empty() && k - tmp.back() >= tmp.back() - i) tmp.pop_back();
                    tmp.push_back(i);
                }
            }
            cur = tmp; ans = cur[0];
            for(Rint i = 1;i < cur.size();++ i) ans = cmp(ans, cur[i], k + 1);
            printf("%d%c", ans + 1, " 
    "[k == n - 1]);
        }
    }
    
  • 相关阅读:
    log4j配置
    Fragment配合RadioGroup实现点击切换布局
    (转)[原] Android 自定义View 密码框 例子
    标题栏透明度变化
    Android 监听ScrollView的滑动
    Android进度条学习
    Android-正方形的容器
    Android添加图片到ListView或者 RecyclerView显示
    Android打开相机和打开相册
    2020新年快乐
  • 原文地址:https://www.cnblogs.com/AThousandMoons/p/13068410.html
Copyright © 2020-2023  润新知