https://www.luogu.org/problem/P3804
求子串的出现次数。用类似拓扑排序的思想,从没有出度的节点开始把他的cnt加在他的后缀连接上。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Node {
int len, link, ch[26];
//额外信息
int cnt, head;
void NewNode(int l = 0) {
len = l, memset(ch, 0, sizeof(ch)), link = 0;
cnt = 1, head = 0;
}
void CopyNewNode(const int &l, const Node& n) {
len = l, memcpy(ch, n.ch, sizeof(ch)), link = n.link;
cnt = 0, head = 0;
}
};
const int MAXN = 1000000;
struct Edge {
int to, next;
} edge[(MAXN << 1) + 5];
int etop;
//SuffixAutomaton
struct SAM {
Node nd[(MAXN << 1) + 5];
int top, last; //top为节点个数,last末尾节点的位置
void init() {
nd[1].NewNode();
top = 1, last = 1;
}
void extend(char ch) {
int c = ch - 'a';
int p = last, x = last = ++top;
nd[x].NewNode(nd[p].len + 1);
for(; p && !nd[p].ch[c]; p = nd[p].link)
nd[p].ch[c] = x;
if(!p)
nd[x].link = 1;
else {
int q = nd[p].ch[c];
if(nd[p].len + 1 == nd[q].len)
nd[x].link = q;
else {
int y = ++top;
nd[y].CopyNewNode(nd[p].len + 1, nd[q]);
nd[q].link = nd[x].link = y;
for(; p && nd[p].ch[c] == q; p = nd[p].link)
nd[p].ch[c] = y;
}
}
}
void dfs(int id) {
for(int i = nd[id].head; i; i = edge[i].next) {
dfs(edge[i].to);
nd[id].cnt += nd[edge[i].to].cnt;
}
}
ll solve() {
//计算每一种子串的数量
etop = 0;
for(int i = top; i >= 2; --i) {
++etop;
edge[etop].to = i;
edge[etop].next = nd[nd[i].link].head;
nd[nd[i].link].head = etop;
}
dfs(1);
ll res = 0;
for(int i = top; i >= 2; --i)
if(nd[i].cnt > 1) {
ll tmp = 1ll * nd[i].cnt * nd[i].len;
if(tmp > res)
res = tmp;
}
return res;
}
} sam;
char s[MAXN + 5];
int main() {
#ifdef Yinku
freopen("Yinku.in", "r", stdin);
#endif // Yinku
scanf("%s", s);
sam.init();
for(int i = 0; s[i] != ' '; ++i)
sam.extend(s[i]);
printf("%lld
", sam.solve());
}
这个link树不如回文机优美,但是可以用拓扑排序来非递归求。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Node {
int len, link, ch[26];
//额外信息
int cnt;
void NewNode(int l = 0) {
len = l, memset(ch, 0, sizeof(ch)), link = 0;
cnt = 1;
}
void CopyNewNode(const int &l, const Node& n) {
len = l, memcpy(ch, n.ch, sizeof(ch)), link = n.link;
cnt = 0;
}
};
const int MAXN = 1000000;
//SuffixAutomaton
struct SAM {
Node nd[(MAXN << 1) + 5];
int top, last; //top为节点个数,last末尾节点的位置
void init() {
nd[1].NewNode();
top = 1, last = 1;
}
void extend(char ch) {
int c = ch - 'a';
int p = last, x = last = ++top;
nd[x].NewNode(nd[p].len + 1);
for(; p && !nd[p].ch[c]; p = nd[p].link)
nd[p].ch[c] = x;
if(!p)
nd[x].link = 1;
else {
int q = nd[p].ch[c];
if(nd[p].len + 1 == nd[q].len)
nd[x].link = q;
else {
int y = ++top;
nd[y].CopyNewNode(nd[p].len + 1, nd[q]);
nd[q].link = nd[x].link = y;
for(; p && nd[p].ch[c] == q; p = nd[p].link)
nd[p].ch[c] = y;
}
}
}
int d[(MAXN << 1) + 5];
int que[(MAXN << 1) + 5], front, back;
ll solve() {
//计算每一种子串的数量
memset(d, 0, sizeof(d));
front = 1, back = 0;
for(int i = top; i >= 2; --i)
++d[nd[i].link];
for(int i = top; i >= 2; --i)
if(!d[i])
que[++back] = i;
while(back >= front) {
int u = que[front], v = nd[que[front]].link;
nd[v].cnt += nd[u].cnt;
--d[v];
if(!d[v])
que[++back] = v;
++front;
}
ll res = 0;
for(int i = top; i >= 2; --i)
if(nd[i].cnt > 1) {
ll tmp = 1ll * nd[i].cnt * nd[i].len;
if(tmp > res)
res = tmp;
}
return res;
}
} sam;
char s[MAXN + 5];
int main() {
#ifdef Yinku
freopen("Yinku.in", "r", stdin);
#endif // Yinku
scanf("%s", s);
sam.init();
for(int i = 0; s[i] != ' '; ++i)
sam.extend(s[i]);
printf("%lld
", sam.solve());
}
https://vjudge.net/contest/31226#problem/A
两个字符串ST的最长公共子串,对S构造SAM。然后逐个比较T的前缀。其实是逐个加入T的字符c,每次都尝试往后走到c,成功则len+1,否则退后到他的后缀连接上,同时更新len,直到没有办法继续后缀连接则这个字符彻底失配。比dp解法快多了。
记得要偏移’a'就都偏移'a',别演戏。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Node {
int len, link;
int ch[26];
//额外信息
//int cnt;
void NewNode(int l = 0) {
len = l, link = 0;
memset(ch, 0, sizeof(ch));
//cnt = 1;
}
void CopyNewNode(const int &l, const Node& n) {
len = l, link = n.link;
memcpy(ch, n.ch, sizeof(ch));
//cnt = 0;
}
};
const int MAXN = 250000;
//SuffixAutomaton
struct SAM {
Node nd[(MAXN << 1) + 5];
int top, last; //top为节点个数,last末尾节点的位置
void init() {
nd[1].NewNode();
top = 1, last = 1;
}
void extend(char ch) {
int c = ch - 'a';
int p = last, x = last = ++top;
nd[x].NewNode(nd[p].len + 1);
for(; p && !nd[p].ch[c]; p = nd[p].link)
nd[p].ch[c] = x;
if(!p)
nd[x].link = 1;
else {
int q = nd[p].ch[c];
if(nd[p].len + 1 == nd[q].len)
nd[x].link = q;
else {
int y = ++top;
nd[y].CopyNewNode(nd[p].len + 1, nd[q]);
nd[q].link = nd[x].link = y;
for(; p && nd[p].ch[c] == q; p = nd[p].link)
nd[p].ch[c] = y;
}
}
}
int LongestCommonSubstring(char *t) {
int ans = 0;
int u = 1, len = 0;
int n = strlen(t);
int endpos = 0;
for(int i = 0; i < n; ++i) {
int c = t[i] - 'a';
while(!nd[u].ch[c] && nd[u].link) {
u = nd[u].link;
len = nd[u].len;
}
if(nd[u].ch[c]) {
u = nd[u].ch[c];
++len;
if(len > ans) {
ans = len;
endpos = i;
}
}
}
/*for(int i = endpos - ans + 1; i <= endpos; ++i)
putchar(t[i]);
putchar('
');*/
return ans;
}
} sam;
char s[MAXN + 5], t[MAXN + 5];
int main() {
#ifdef Yinku
freopen("Yinku.in", "r", stdin);
#endif // Yinku
scanf("%s%s", s, t);
sam.init();
for(int i = 0; s[i] != ' '; ++i)
sam.extend(s[i]);
printf("%d
", sam.LongestCommonSubstring(t));
}
多个字符串的LCS。又WA又T。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
struct Node {
int len, link;
int ch[26];
void NewNode(int l = 0) {
len = l, link = 0;
//memset(ch, 0, sizeof(ch));
}
void CopyNewNode(const int &l, const Node& n) {
len = l, link = n.link;
memcpy(ch, n.ch, sizeof(ch));
}
};
const int MAXLEN = 100000;
const int MAXNODE = 2 * MAXLEN + 5;
//SuffixAutomaton
struct SAM {
Node nd[MAXNODE];
int top, last; //top为节点个数,last末尾节点的位置
int minlen[MAXNODE], curlen[MAXNODE];
void init() {
nd[1].NewNode();
top = 1, last = 1;
memset(minlen, INF, sizeof(minlen));
}
void extend(char ch) {
int c = ch - 'a';
int p = last, x = last = ++top;
nd[x].NewNode(nd[p].len + 1);
for(; p && !nd[p].ch[c]; p = nd[p].link)
nd[p].ch[c] = x;
if(!p)
nd[x].link = 1;
else {
int q = nd[p].ch[c];
if(nd[p].len + 1 == nd[q].len)
nd[x].link = q;
else {
int y = ++top;
nd[y].CopyNewNode(nd[p].len + 1, nd[q]);
nd[q].link = nd[x].link = y;
for(; p && nd[p].ch[c] == q; p = nd[p].link)
nd[p].ch[c] = y;
}
}
}
int d[MAXNODE];
int que[MAXNODE], front, back;
void updateLink() {
//别忘记沿着link更新各位祖先的长度
front = 1, back = 0;
for(int i = top; i >= 1; --i)
++d[nd[i].link];
for(int i = top; i >= 1; --i)
if(!d[i])
que[++back] = i;
while(back >= front) {
int u = que[front++], v = nd[u].link;
//它的link祖先的LCS长度不能超过他自己的长度
int t = curlen[u] < nd[v].len ? curlen[u] : nd[v].len;
if(t > curlen[v])
curlen[v] = t;
if(minlen[u] > curlen[u])
minlen[u] = curlen[u];
curlen[u] = 0;
if(!(--d[v]))
que[++back] = v;
}
}
void LongestCommonSubstringHelp(char *t) {
int u = 1, len = 0;
for(int i = 0; t[i]; ++i) {
int c = t[i] - 'a';
while(!nd[u].ch[c] && nd[u].link) {
u = nd[u].link;
len = nd[u].len;
}
if(nd[u].ch[c]) {
u = nd[u].ch[c];
++len;
if(len > curlen[u])
curlen[u] = len;
}
}
updateLink();
return;
}
int LongestCommonSubstring() {
int ans = 0;
for(int i = top; i >= 1; --i)
ans = max(ans, minlen[i]);
return ans;
}
} sam;
char s[MAXLEN], t[MAXLEN];
int main() {
#ifdef Yinku
freopen("Yinku.in", "r", stdin);
#endif // Yinku
scanf("%s", s);
sam.init();
for(int i = 0; s[i] != ' '; ++i)
sam.extend(s[i]);
while(~scanf("%s", t))
sam.LongestCommonSubstringHelp(t);
printf("%d
", sam.LongestCommonSubstring());
}