后缀自动机

#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();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值