Luogu5334 [JSOI2019] 节日庆典 【ExKMP】

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

数据范围:\(n\le 3\times 10^6\)


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

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

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

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

性质2:对于 \(i<j\),若 \(\text{lcp}(S[i:k],S[j:k])>k-j\),且 \(k-j\ge 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-j\ge 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(n\log 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, " \n"[k == n - 1]);
    }
}
posted @ 2020-06-08 20:51  mizu164  阅读(168)  评论(0编辑  收藏  举报