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;
}