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]);
    }
}
原文地址:https://www.cnblogs.com/AThousandMoons/p/13068410.html