CF126B password SAM做法()

题意

找出 \(S\) 的最长的子串 \(T\),满足 \(T\) 是 \(S\) 的前缀、后缀,并在中间出现过。不存在则输出

Just a Legend

思路如下:

  • 题目要求目标串是 Border,并且在中间出现过,即要求子串是 Border ,并且出现次数大于等于 3 次。
  • 那么我们记录字符串末尾在 SAM 中的位置 pos,然后跳它到根的后缀链接,显然途中经过的子串集合一定是 \(S\) 串的后缀,但它不一定是前缀,那么如何知道它是前缀呢?
  • 首先记录每个节点的 \(right\) (endpos) 集合大小为 num[] 。然后记录每个节点的 \(right\) 集合的最小值为 rightmin[] 。
  • 然后我们就从 pos 节点往上跳后缀链接树,判断每个节点的最大长度是否大于 rightmin 的值并且出现次数(更正式地, \(|right|\geq 3\)),满足条件就更新一下答案,至于用最大长度还是 rightmin 值去更新。
  • 时间复杂度 \(O(n)\) 。

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef unsigned long long ull;
typedef double db;
#define re _read
#define arr(x) (x).begin(),(x).end()
#define get_sz(v) (int)v.size()
#define fi first
#define se second
#define pb push_back
#define endl "\n"
using namespace std;
template <class T> inline void _read(T& x) {
    static long long ans;
    static unsigned int c;
    static bool p;
    for (c = getchar(); c != '-' && (c < '0' || c > '9'); c = getchar()) ;
    if (c == '-')
        p = false, c = getchar();
    else
        p = true;
    for (ans = 0; c <= '9' && c >= '0'; c = getchar())
        ans = ans * 10 + c - '0';
    x = p ? ans : -ans;
}
/*====================================================================================================================================================================================*/
const int N = 1e6 + 10;
char s[N];
int pos;

const int maxn = 1e6 + 10;
struct SAM {
    //basic
    const char* s;
    int last, cnt, len;
    int nxt[maxn * 2][26],fa[maxn * 2],l[maxn * 2], rightmin[maxn * 2];
    //extension
    int cntA[maxn * 2], id[maxn * 2];/*辅助拓扑更新*/
    int num[maxn * 2];/*每个节点代表的所有串的出现次数*/
    SAM () { clear(); }
    void clear() {
        last = cnt = 1, l[1] = fa[1] = 0, memset(nxt[1], 0, sizeof nxt[1]);
    }
    void init(const char * str, int _len) {
        s = str, len = _len;
        for (int i = 1; i <= _len; i++)
            extend(str[i] - 'a', i);
    }
    void extend(int c, int idx) {
        int p = last, np = ++cnt;
        rightmin[np] = idx;
        if (idx == len) pos = np;
        memset(nxt[cnt], 0, sizeof nxt[cnt]);
        l[np] = l[p] + 1, last = np;
        while (p && !nxt[p][c]) nxt[p][c] = np, p = fa[p];
        if (!p) fa[np] = 1;
        else {
            int q = nxt[p][c];
            if (l[q] == l[p] + 1) fa[np] = q;
            else {
                int nq = ++cnt;
                rightmin[nq] = rightmin[q];
                l[nq] = l[p] + 1;
                memcpy(nxt[nq], nxt[q], sizeof(nxt[q]));
                fa[nq] = fa[q], fa[np] = fa[q] = nq;
                while (nxt[p][c] == q) nxt[p][c] = nq, p = fa[p];
            }
        }
    }
    void build() {
        memset(cntA, 0, sizeof cntA);
        memset(num, 0, sizeof num);
        for (int i = 1; i <= cnt; i++) cntA[l[i]]++;
        for (int i = 1; i <= cnt; i++) cntA[i] += cntA[i - 1];
        for (int i = cnt; i >= 1; i--) id[cntA[l[i]]--] = i;
        /*更新主串节点*/
        int temp = 1;
        for (int i = 1; i <= len; i++) {
            num[temp = nxt[temp][s[i] - 'a']] = 1;
        }
        /*拓扑更新*/
        for (int i = cnt; i >= 1; i--) {
            // basic
            int x = id[i];
            num[fa[x]] += num[x];
            // extension
        }
        // extension
    }
    void debug(){
        for (int i = cnt; i >= 1; i--){
            printf("num[%d]=%d l[%d]=%d fa[%d]=%d\n",i,num[i],i,l[i],i,fa[i]);
        }
    }
}sam;

int main() {
    scanf("%s", s + 1);
    int len = strlen(s + 1);
    sam.init(s, len);
    sam.build();
    int ans = 0;
    while (pos) {
        if (sam.num[pos] >= 3 & sam.l[pos] >= sam.rightmin[pos]) ans = max(ans, sam.rightmin[pos]);
        pos = sam.fa[pos];
    }
    if (!ans) puts("Just a legend");
    else {
        for (int i = 1; i <= ans; i++)
            printf("%c", s[i]);
    }
    return 0;
}
————————

题意

找出 \(S\) 的最长的子串 \(T\),满足 \(T\) 是 \(S\) 的前缀、后缀,并在中间出现过。不存在则输出

Just a Legend

思路如下:

  • 题目要求目标串是 Border,并且在中间出现过,即要求子串是 Border ,并且出现次数大于等于 3 次。
  • 那么我们记录字符串末尾在 SAM 中的位置 pos,然后跳它到根的后缀链接,显然途中经过的子串集合一定是 \(S\) 串的后缀,但它不一定是前缀,那么如何知道它是前缀呢?
  • 首先记录每个节点的 \(right\) (endpos) 集合大小为 num[] 。然后记录每个节点的 \(right\) 集合的最小值为 rightmin[] 。
  • 然后我们就从 pos 节点往上跳后缀链接树,判断每个节点的最大长度是否大于 rightmin 的值并且出现次数(更正式地, \(|right|\geq 3\)),满足条件就更新一下答案,至于用最大长度还是 rightmin 值去更新。
  • 时间复杂度 \(O(n)\) 。

Solution

#include<bits/stdc++.h>
typedef long long ll;
typedef std::pair<int, int> PII;
typedef std::pair<ll, ll> PLL;
typedef unsigned long long ull;
typedef double db;
#define re _read
#define arr(x) (x).begin(),(x).end()
#define get_sz(v) (int)v.size()
#define fi first
#define se second
#define pb push_back
#define endl "\n"
using namespace std;
template <class T> inline void _read(T& x) {
    static long long ans;
    static unsigned int c;
    static bool p;
    for (c = getchar(); c != '-' && (c < '0' || c > '9'); c = getchar()) ;
    if (c == '-')
        p = false, c = getchar();
    else
        p = true;
    for (ans = 0; c <= '9' && c >= '0'; c = getchar())
        ans = ans * 10 + c - '0';
    x = p ? ans : -ans;
}
/*====================================================================================================================================================================================*/
const int N = 1e6 + 10;
char s[N];
int pos;

const int maxn = 1e6 + 10;
struct SAM {
    //basic
    const char* s;
    int last, cnt, len;
    int nxt[maxn * 2][26],fa[maxn * 2],l[maxn * 2], rightmin[maxn * 2];
    //extension
    int cntA[maxn * 2], id[maxn * 2];/*辅助拓扑更新*/
    int num[maxn * 2];/*每个节点代表的所有串的出现次数*/
    SAM () { clear(); }
    void clear() {
        last = cnt = 1, l[1] = fa[1] = 0, memset(nxt[1], 0, sizeof nxt[1]);
    }
    void init(const char * str, int _len) {
        s = str, len = _len;
        for (int i = 1; i <= _len; i++)
            extend(str[i] - 'a', i);
    }
    void extend(int c, int idx) {
        int p = last, np = ++cnt;
        rightmin[np] = idx;
        if (idx == len) pos = np;
        memset(nxt[cnt], 0, sizeof nxt[cnt]);
        l[np] = l[p] + 1, last = np;
        while (p && !nxt[p][c]) nxt[p][c] = np, p = fa[p];
        if (!p) fa[np] = 1;
        else {
            int q = nxt[p][c];
            if (l[q] == l[p] + 1) fa[np] = q;
            else {
                int nq = ++cnt;
                rightmin[nq] = rightmin[q];
                l[nq] = l[p] + 1;
                memcpy(nxt[nq], nxt[q], sizeof(nxt[q]));
                fa[nq] = fa[q], fa[np] = fa[q] = nq;
                while (nxt[p][c] == q) nxt[p][c] = nq, p = fa[p];
            }
        }
    }
    void build() {
        memset(cntA, 0, sizeof cntA);
        memset(num, 0, sizeof num);
        for (int i = 1; i <= cnt; i++) cntA[l[i]]++;
        for (int i = 1; i <= cnt; i++) cntA[i] += cntA[i - 1];
        for (int i = cnt; i >= 1; i--) id[cntA[l[i]]--] = i;
        /*更新主串节点*/
        int temp = 1;
        for (int i = 1; i <= len; i++) {
            num[temp = nxt[temp][s[i] - 'a']] = 1;
        }
        /*拓扑更新*/
        for (int i = cnt; i >= 1; i--) {
            // basic
            int x = id[i];
            num[fa[x]] += num[x];
            // extension
        }
        // extension
    }
    void debug(){
        for (int i = cnt; i >= 1; i--){
            printf("num[%d]=%d l[%d]=%d fa[%d]=%d\n",i,num[i],i,l[i],i,fa[i]);
        }
    }
}sam;

int main() {
    scanf("%s", s + 1);
    int len = strlen(s + 1);
    sam.init(s, len);
    sam.build();
    int ans = 0;
    while (pos) {
        if (sam.num[pos] >= 3 & sam.l[pos] >= sam.rightmin[pos]) ans = max(ans, sam.rightmin[pos]);
        pos = sam.fa[pos];
    }
    if (!ans) puts("Just a legend");
    else {
        for (int i = 1; i <= ans; i++)
            printf("%c", s[i]);
    }
    return 0;
}