#include <bits/stdc++.h>
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define ROF(i, a, b) for (int i = (a); i >= (b); i--)
#define ll long long
using namespace std;
const int N=1e6+5;
struct SAM{
int size, last; //最大点编号,上一个点编号
ll ans=0;
vector<int> len, link, sz; //maxlen,后缀链接
vector<vector<int>> to; //trans数组,记录加一个字符后的转移
vector<int> G[N<<1];
// 字符串长度*2(必须是两倍大小),字符集大小(一般是26或52)
SAM(int strLen, int chSize) : size(1), last(1) {
len.resize(strLen, 0);
link.resize(strLen, 0);
sz.resize(strLen, 0);
to.resize(strLen, vector<int>(chSize, 0));
}
// 为当前的SAM增加一个字符c
void extend(int c){
int p, cur = ++size; //新建一个结点(因为S[1..i+1]必定是一个新状态)
len[cur] = len[last]+1; //S[1...i+1].len = S[1...i] + 1
sz[cur] = 1;
//情况1,或者情况2的前半部分
for(p=last; p && !to[p][c]; p=link[p]){
to[p][c] = cur; //如果前面的状态没有连c,那就现在连上
}
if(!p) link[cur] = 1; //如果一直到原点,都是没有连过c,那就是满足情况1,link接到起始点1
else{ //否则就是中间有的点连过c,满足情况2
int q = to[p][c]; //p点有连过c,连了c的state在q这里
if(len[q]==len[p]+1) link[cur]=q; //情况2-A类。不用拆分,直接连q
else{ //情况2-B类,需要拆分q点,拆分出一个cl结点
int cl = ++size; //拆分出的新结点
len[cl] = len[p]+1; //把能连link的部分拆分到cl这里(就是满足len[p]+1的部分)
to[cl] = to[q]; //复制所有原结点的trans连接
link[cl] = link[q]; //后缀重连
link[cur] = link[q] = cl; //后缀重连
while(p && to[p][c]==q){ //把之前trans到q的点全部改成trans到cl(因为trans指向state中最短的字符串,而这个串在cl那里)
to[p][c] = cl;
p = link[p];
}
}
}
//cout<<size<<' ';
last = cur; //最后一步,记录末尾结点
}
//后缀链接树
void BLT(){
for(int i=1;i<=size;i++) G[link[i]].push_back(i);
}
//遍历后缀链接树
void dfs(int u){
for(int v:G[u]) dfs(v), sz[u]+=sz[v];
if(sz[u]>1) ans=max(ans,1ll*sz[u]*len[u]);
//cout<<ans<<' ';
}
};
SAM sam((int)2e6+10, 26); //特别注意,后缀自动机的第一位是两倍的字符串长度!!
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
string s; cin>>s; for(char ch:s) sam.extend(ch-'a');
sam.BLT();
sam.dfs(1);
cout<<sam.ans<<'\n';
}
字典序第k大
#include <bits/stdc++.h>
using namespace std;
#define FOR(i, a, b) for (int i = (a); i <= (b); i++)
#define int long long
typedef pair<int,int> pii;
const int N = 5e5+5;
char s[N<<1];
int t,k;
vector<int> g[N<<1];
int f[N<<1];
struct SAM{
int tot=1, last=1; //最大点编号,上一个点编号
int len[N<<1], fa[N<<1], sz[N<<1];
int to[N<<1][26];
// 为当前的SAM增加一个字符c
void extend(int c){
int p, cur = ++tot; //新建一个结点(因为S[1..i+1]必定是一个新状态)
len[cur] = len[last]+1; //S[1...i+1].len = S[1...i] + 1
sz[cur] = 1; //初始sz值都是1
//情况1,或者情况2的前半部分
for(p=last; p && !to[p][c]; p=fa[p]){
to[p][c] = cur; //如果前面的状态没有连c,那就现在连上
}
if(!p) fa[cur] = 1; //如果一直到原点,都是没有连过c,那就是满足情况1,link接到起始点1
else{ //否则就是中间有的点连过c,满足情况2
int q = to[p][c]; //p点有连过c,连了c的state在q这里
if(len[q]==len[p]+1) fa[cur]=q; //情况2-A类。不用拆分,直接连q
else{ //情况2-B类,需要拆分q点,拆分出一个cl结点
int cl = ++tot; //拆分出的新结点
len[cl] = len[p]+1; //把能连link的部分拆分到cl这里(就是满足len[p]+1的部分)
// to[cl] = to[q]; //复制所有原结点的trans连接
memcpy(to[cl], to[q], sizeof(to[q]));
fa[cl] = fa[q]; //后缀重连
fa[cur] = fa[q] = cl; //后缀重连
while(p && to[p][c]==q){ //把之前trans到q的点全部改成trans到cl(因为trans指向state中最短的字符串,而这个串在cl那里)
to[p][c] = cl;
p = fa[p];
}
}
}
last = cur; //最后一步,记录末尾结点
}
//建立后缀树
void build(){
FOR(i,2,tot) g[fa[i]].push_back(i);
}
//dfs计算size
void dfs(int u){
for(int v:g[u]) dfs(v), sz[u]+=sz[v];
}
//dfs2计算所能到达的点的点权和
int dfs2(int u){
if(f[u]) return f[u]; //计算过,直接返回
f[u] = sz[u];
for(int i=0; i<26; i++){
int v = to[u][i]; //u可以to的26个点
if(v) f[u] += dfs2(v);
}
return f[u];
}
//当前在sam的u点,要求打印子树第k小
void print(int u,int k){
if(k>f[u]) {cout<<"-1"; return;} //没有第k小,结束
if(k<=sz[u]) return;
k -= sz[u];
for(int i=0; i<26; i++){
int v = to[u][i];
if(k>f[v]) k-=f[v];
else{
cout<<(char)('a'+i);
print(v,k);
return;
}
}
}
} sam;
void solve(){
cin>>(s+1)>>t>>k;
int len = strlen(s+1);
FOR(i,1,len) sam.extend(s[i]-'a');
sam.build(); //建立后缀树
if(t==0) FOR(i,1,sam.tot) sam.sz[i]=1;
else sam.dfs(1);
sam.sz[1] = 0; //注意根结点是空字符串,按照题目意思,出现次数为0
sam.dfs2(1); //再次dfs,计算出现次数
sam.print(1,k);
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T=1; //cin>>T;
while(T--) solve();
}