[TJOI2015] 弦论 题解

所有子串,一眼 \(\text{SAM}\)

从根开始一直往下走,走到任何一个点都代表一个子串。维护 \(sm\) 表示每个子串有几个(\(t=0\) 就当一个),可以用树形 \(dp\) 跳后缀链接树,然后暴力跑 \(\text{SAM}\) 即可。

当然我们发现这样时间复杂度会爆炸,因为第二部分是个 \(TAG\),因此用 \(cnt\) 记录每个节点能走到的所有节点的 \(sm\) 之和,这样就能少判断很多情况。

时间复杂度 \(O(26n)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e6+5;
int k,t,ans[N],len;
struct SAM{
	int sm[N],ln[N],sz[N];
	int id,tl,tr[N][26],pr[N];
	vector<int>g[N];int cnt[N];
	SAM(){pr[0]=-1;}
	void cpy(int x,int y){
		for(int i=0;i<26;i++)
			tr[x][i]=tr[y][i];
		pr[x]=pr[y];ln[x]=ln[y];
	}void add(int x){
		ln[++id]=ln[tl]+1;
		int p=tl;tl=id;
		while(~p&&!tr[p][x])
			tr[p][x]=id,p=pr[p];
		if(p<0){
			sm[tl]++;
			return;
		}int u=p,v=tr[p][x];
		if(ln[u]+1==ln[v]){
			pr[id]=v;
			sm[tl]++;
			return;
		}cpy(++id,v);
		ln[id]=ln[u]+1;
		pr[v]=pr[id-1]=id;
		while(~p&&tr[p][x]==v)
			tr[p][x]=id,p=pr[p];
		sm[tl]++;
	}void dfs(int x){
		for(int i=0;i<g[x].size();i++)
			dfs(g[x][i]),sm[x]+=sm[g[x][i]];
	}void find(int x){
		if(k<=0) return;
		for(int i=0;i<26;i++){
			int y=tr[x][i];
			if(!y) continue;
			if(!cnt[y]){
				int p=k;ans[++len]=i;
				k-=sm[y];find(y);
				if(k<=0) return; 
				cnt[y]=p-k;len--;
			}else if(k<=cnt[y]){
				ans[++len]=i;
				k-=sm[y];find(y);
				return;
			}else k-=cnt[y];
		} 
	}void jb(){
		for(int i=1;i<=id;i++)
			g[pr[i]].push_back(i);
	}
}sam;char s[N];
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>s>>t>>k;
	for(int i=0;s[i];i++)
		sam.add(s[i]-'a');
	sam.jb();
	if(t) sam.dfs(0);
	else for(int i=1;i<=sam.id;i++) sam.sm[i]=1;
	sam.find(0);
	for(int i=1;i<=len;i++)
		cout<<(char)(ans[i]+'a');
	return 0;
}//man!what can I say!
posted @ 2024-07-10 08:09  长安一片月_22  阅读(7)  评论(0编辑  收藏  举报