[TJOI2015]弦论

VI.[TJOI2015]弦论

SAM应用4:求字典序第 \(k\) 大子串。

前面说过,自动机部分接受且仅接受原串后缀,但实际上自动机中节点都是后缀的前缀,即子串。于是在自动机上先倒着拓扑DP一下,然后正着扫一遍即可求出第 \(k\) 大子串。而当相同子串计算多次时,我们知道一个子串的出现次数即为其所属 \(\text{enspos}\) 集合中点数。于是这时DP时每条边的权值设作集合点数即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int cnt=1;
struct Suffix_Automaton{
	int ch[26],fa,len;
}t[1000100];
int sz[1000100];
int Add(int x,int c){
	int xx=++cnt;t[xx].len=t[x].len+1;
	sz[xx]=1;
	for(;x&&!t[x].ch[c];x=t[x].fa)t[x].ch[c]=xx;
	if(!x){t[xx].fa=1;return xx;}
	int y=t[x].ch[c];
	if(t[y].len==t[x].len+1){t[xx].fa=y;return xx;}
	int yy=++cnt;t[yy]=t[y];
	t[yy].len=t[x].len+1;
	t[xx].fa=t[y].fa=yy;
	for(;x&&t[x].ch[c]==y;x=t[x].fa)t[x].ch[c]=yy;
	return xx;
}
int n,m,tp,in[1000100];
char s[500100];
ll f[1000100];
queue<int>q;
vector<int>v[1000100],u[1001000];
void dfs(int x){for(auto y:u[x])dfs(y),sz[x]+=sz[y];}
int main(){
	scanf("%s%d%d",s,&tp,&m),n=strlen(s);
	for(int i=0,las=1;i<n;i++)las=Add(las,s[i]-'a');
	for(int i=1;i<=cnt;i++)for(int j=0;j<26;j++)if(t[i].ch[j])v[t[i].ch[j]].push_back(i),in[i]++;
	for(int i=1;i<=cnt;i++)u[t[i].fa].push_back(i);
	dfs(1);
	for(int i=1;i<=cnt;i++)if(!in[i])q.push(i);
	while(!q.empty()){
		int x=q.front();q.pop();
		for(auto y:v[x]){
			f[y]+=f[x]+(tp?sz[x]:1);
			if(!--in[y])q.push(y);
		}
	}
	if(m>f[1]){puts("-1");return 0;}
	for(int x=1;m>0;)for(int i=0,y;i<26;i++){
		if(!(y=t[x].ch[i]))continue;
		if(m>f[y]+(tp?sz[y]:1))m-=f[y]+(tp?sz[y]:1);else{putchar('a'+i),m-=(tp?sz[y]:1),x=y;break;}		
	}
	return 0;
}

原文地址:https://www.cnblogs.com/Troverld/p/14605690.html