https://oi-wiki.org/string/sam/#_5
oiwiki网上的
https://blog.csdn.net/thy_asdf/article/details/51569443
这个博客讲了很多题。
//#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 2e4 + 5;
const int maxc = 180;//如果太大可以用map
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
typedef long long ll;
int len[maxn * 2]; //最长子串的长度(该节点字串数量=len[x]-len[fail[x]])
int fail[maxn * 2]; //后缀链接(最短串前部减少一个字符所到达的状态)
int cnt[maxn * 2]; //被后缀连接的数量,方法一求拓扑排序时需要。
int nex[maxn * 2][maxc]; //状态转移(尾部加一个字符的下一个状态)(图),如果不只是字母,而是很大的话可以用map.
int sz; //节点编号
int last; //最后节点
ll epos[maxn * 2]; // enpos数(该状态子串出现数量)
ll sum[maxn * 2], rak[maxn * 2]; //求拓扑序是用的数组。
int val[maxn], mi[maxn * 2], ma[maxn * 2];
/**
初始化
**/
void init() { //初始化
last = sz = 1; //1表示root起始点 空集
fail[1] = len[1] = 0;
}
/**
SAM建图
**/
void Extend(int c) { //插入字符,为字符ascll码值
int cur = ++sz; //创建一个新节点cur;
len[cur] = len[last] + 1; // 长度等于最后一个节点+1
mi[cur] = ma[cur] = len[cur];
epos[cur] = 1; //接受节点子串除后缀连接还需加一
int p; //第一个有C转移的节点;
for (p = last; p && !nex[p][c]; p = fail[p]) nex[p][c] = cur;//沿着后缀连接 将所有没有字符c转移的节点直接指向新节点
if (!p) {
fail[cur] = 1;
cnt[1]++; //全部都没有c的转移 直接将新节点后缀连接到起点
}
else {
int q = nex[p][c]; //p通过c转移到的节点
if (len[p] + 1 == len[q]) //pq是连续的
fail[cur] = q;
cnt[q]++; //将新节点后缀连接指向q即可,q节点的被后缀连接数+1
else {
int nq = ++sz; //不连续 需要复制一份q节点
len[nq] = len[p] + 1; //令nq与p连续
fail[nq] = fail[q]; //因后面fail[q]改变此处不加cnt
memcpy(nex[nq], nex[q], sizeof(nex[q])); //复制q的信息给nq
for (; p&&nex[p][c] == q; p = fail[p])
nex[p][c] = nq; //沿着后缀连接 将所有通过c转移为q的改为nq
fail[q] = fail[cur] = nq; //将cur和q后缀连接改为nq
cnt[nq] += 2; // nq增加两个后缀连接
}
}
last = cur; //更新最后处理的节点
}
char s1[maxn], s2[maxn];
/**
求一个串每个长度的所有子串中,出现最多的次数spoj8222
**/
/**
方法一:bfs的拓扑排序,不是主要方法
**/
//求npos数,即该节点子串出现次数
void GetNpos(char ch[], int len1) {
for(int i = 0; i < len1; i++) Extend(ch[i] - 'a');
queue<int>q;
for (int i = 1; i <= sz; i++)
if (!cnt[i]) q.push(i); //将所有没被后缀连接指向的节点入队
while (!q.empty()) {
int x = q.front();
q.pop();
epos[fail[x]] += epos[x]; //子串数量等于所有后缀连接指向该节点的子串数量和+是否为接受节点
if (--cnt[fail[x]] == 0)q.push(fail[x]); //当所有后缀连接指向该节点的处理完毕后再入队
}
}
//求出所有长度为k的子串中出现次数最多的子串出现次数
void GetSubMax() {
ll a[maxn];
scanf("%s", s1);//方法一长度为i的子串出现最大次数
int len1 = strlen(s1);
GetNpos(s1, len1);
for (int i = 1; i <= sz; i++) a[len[i]] = max(a[len[i]], epos[i]); //长度≤k的子串中出现次数最多的子串出现次数的最小值
for (int i = len1 - 1; i >= 1; i--) a[i] = max(a[i], a[i + 1]); //求一遍后缀最大值就是答案
for (int i = 1; i <= len1; i++) printf("%lld\n", a[i]);
}
/**
方法二数组的逆拓扑序
**/
void getmaxlen() {
ll num[maxn * 2];//方法二长度为i的子串出现最大次数。
init();
scanf("%s", s1);
int len1 = strlen(s1);
for(int i = 0; i < len1; i++) Extend(s1[i] - 'a');
//下面是计数排序的思想。相当于找一个数前面有多少个数,从而知道这个数排在哪。
for(int i = 1; i <= sz; i++) sum[len[i] ]++;
for(int i = 1; i <= len1; i++) sum[i] += sum[i - 1];
for(int i = 1; i <= sz; i++) rak[sum[len[i] ]-- ] = i;
for(int i = sz; i >= 1; i--) {
int x = rak[i];
epos[fail[x] ] += epos[x];
}
for(int i = 1; i <= sz; i++) num[len[i] ] = max(num[len[i] ], epos[i]);
for(int i = len1 - 1; i >= 1; i--) num[i] = max(num[i], num[i + 1]);
for(int i = 1; i <= len1; i++) printf("%lld\n", num[i]);
}
/**
求不相同字串数量
**/
void GetSubNum() {
ll ans = 0;
for (int i = 2; i <= sz; i++) ans += len[i] - len[fail[i]]; //一状态子串数量等于len[i]-len[fail[i]]
printf("%lld\n",ans);
}
/**
求多个字符串的最长公共子串spoj1812
**/
void getnlcs() {
ll maxnlcs[maxn * 2]; //求多个子串最长公共子串时,每个串来匹配时能匹的最长长度。
ll ans[maxn * 2]; //求多个子串的最长公共子串时的结果数组。
init();
scanf("%s", s1);
int len1 = strlen(s1);
for(int i = 0; i < len1; i++) Extend(s1[i] - 'a');
for(int i = 1; i <= sz; i++) ans[i] = len[i];
//下面是计数排序的思想。相当于找一个数前面有多少个数,从而知道这个数排在哪。
for(int i = 1; i <= sz; i++) sum[len[i] ]++;
for(int i = 1; i <= len1; i++) sum[i] += sum[i - 1];
for(int i = 1; i <= sz; i++) rak[sum[len[i] ]-- ] = i;
while(~scanf("%s", s2)) {
memset(maxnlcs, 0, sizeof(maxnlcs));
int len2 = strlen(s2);
int p = 1;
ll tmp = 0;
for(int i = 0; i < len2; i++) {
int x = s2[i] - 'a';
if(nex[p][x]) {
tmp++;
p = nex[p][x];
}
else {
while(p && !nex[p][x]) p = fail[p];
if(!p) {
tmp = 0;
p = 1;
}
else {
tmp = len[p] + 1;
p = nex[p][x];
}
}
maxnlcs[p] = max(maxnlcs[p], tmp);
}
//首先如果一个点能够匹配到的话,那么他的fail指针的点也一定可以匹配到,因为fail指针的
//的点是原来节点的子串,所以下面的节点要先更新,然后更新其fail指针的。这个需要逆拓扑。
for(int i = sz; i >= 1; i--) {
ll x = rak[i];
ans[x] = min(ans[x], maxnlcs[x]);
if(maxnlcs[x] && fail[x]) maxnlcs[fail[x] ] = len[fail[x] ];
}
// printf("scsc\n");
}
ll res = 0;
for(int i = 1; i <= sz; i++) res = max(ans[i], res);
printf("%lld\n", res);
}
/**
求两个字符串的最长公共子串。spoj1811
直接根据后缀自动机的状态转移图来遍历,如果存在这个字符就继续往下走,不存在则开始跳fail,
直到找到存在那个字符的,此时只有这个fail点与最开始的点后缀相同。
**/
void getlcs() {
init();
scanf("%s%s", s1, s2);
int len1 = strlen(s1), len2 = strlen(s2);
for(int i = 0; i < len1; i++) {
Extend(s1[i] - 'a');
}
int ans = 0, tmp = 0, p = 1;
for(int i = 0; i < len2; i++) {
int x = s2[i] - 'a';
if(nex[p][x]) {
tmp++;
p = nex[p][x];
}
else {
while(p && !nex[p][x]) p = fail[p];
if(!p) {
tmp = 0;
p = 1;
}
else {
tmp = len[p] + 1;
p = nex[p][x];
}
}
ans = max(ans, tmp);
}
printf("%d\n", ans);
}
/**
bzoj3998
求一个字符串中第K大的串,op=0代表相同的串在不同位置只算一次,op=1代表可以算多次。
所以先求出所有子串的可能出现次数。epos数组
然后求出某个点以这个点开头的字符串数量。num数组。
然后在dfs去找。
**/
ll num[maxn * 2];
void dfsk(int rt, int rk) {
if(rk <= epos[rt]) return;
rk -= epos[rt];
for(int i = 0; i < 26; i++) {
int v = nex[rt][i];
if(v) {
if(rk <= num[v]) {
putchar('a' + i);
dfsk(v, rk);
return;
}
else rk -= num[v];
}
}
}
void getk() {
scanf("%s", s1);
int op, k;
scanf("%d%d", &op, &k);
init();
int len1 = strlen(s1);
for(int i = 0; i < len1; i++) Extend(s1[i] - 'a');
//下面是计数排序的思想。相当于找一个数前面有多少个数,从而知道这个数排在哪。
for(int i = 1; i <= sz; i++) sum[len[i] ]++;
for(int i = 1; i <= len1; i++) sum[i] += sum[i - 1];
for(int i = 1; i <= sz; i++) rak[sum[len[i] ]-- ] = i;
for(int i = sz; i >= 1; i--) {
int x = rak[i];
if(op == 1) epos[fail[x] ] += epos[x];
else epos[x] = 1;
}
epos[1] = 0;
for(int i = sz; i >= 1; i--) {
int x = rak[i];
num[x] = epos[x];
for(int j = 0; j < 26; j++) {
int v = nex[x][j];
if(v) num[x] += num[v];
}
}
// for(int i = 1; i <= sz; i++) printf("%lld\n", epos[i]);
if(num[1] < k) puts("-1");
else {
dfsk(1, k);
puts("");
}
}
/**
求变化趋势相同的子串且长度大于等于5.poj1743
因为是变化趋势,所以要先差分一下,那么就相当于差分数组建后缀自动机,然后找长度大于等于4的子串,
且没有重合的点。注意多组数据时的初始化。
**/
int tmp[maxn * 2];
int cmp(int x, int y) {
return len[x] > len[y];
}
void getSameTend() {
int n;
while(~scanf("%d", &n)) {
if(n == 0) break;
init();
memset(mi, inf, sizeof(mi));
memset(ma, 0, sizeof(ma));
memset(nex, 0, sizeof(nex));
memset(fail, 0, sizeof(fail));
memset(sum, 0, sizeof(sum));
for(int i = 1; i <= n; i++) scanf("%d", &val[i]);
for(int i = 1; i < n; i++) val[i] = val[i + 1] - val[i], Extend(val[i] + 88);
for(int i = 1; i <= sz; i++) sum[len[i] ]++;
for(int i = 1; i < n; i++) sum[i] += sum[i - 1];
for(int i = 1; i <= sz; i++) rak[sum[len[i] ]-- ] = i;
for(int i = sz; i > 0; i--) {
int x = rak[i];
ma[fail[x] ] = max(ma[fail[x] ], ma[x]);
mi[fail[x] ] = min(mi[fail[x] ], mi[x]);
}
int ans = 0;
for(int i = sz; i >= 1; i--) {
ans = max(ans, min(ma[i] - mi[i], len[i]));
}
ans++;
if(ans < 5) ans = 0;
printf("%d\n", ans);
}
}
int main() {
getlcs();
getnlcs();
getmaxlen();
getk();
getSameTend();
return 0;
}