P3975 [TJOI2015]弦论
一定要记得初始化!
后缀链接连接的节点所表示的字符串是该节点表示字符串的后缀,先将所有新增节点的dp[i]
都置为1(除了拆点),一个节点所表示字符串的出现次数是其子树所有dp
值之和(下标不同,本质相同的两个串是不同串)
如果下标不同,本质相同的两个串属于一个串的话,那么所有的dp[i]
为1即可。因为,后缀树的所有节点包括了所有的子串。然后再按照Next
转移即可。
Next 表示的是转移,通过每个节点的转移,可以得到后缀树中的所有子串
link 指向的是子串,所以 p 指向的节点一定会让 link[p] 表示的字符串在除 link[p] 表示的位置再出现一次。
// Created by CAD
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
namespace sam{
int len[maxn],link[maxn],Next[maxn][26];
int sz,last;
int dp[maxn];
void init(){ //记得初始化
sz=last=0;
len[0]=0,link[0]=-1;
}
void insert(char c){ //插入字符
int now=++sz;
len[now]=len[last]+1;
int p=last;
dp[now]=1;
while(~p&&!Next[p][c-'a']){
Next[p][c-'a']=now;
p=link[p];
}
if(p==-1) link[now]=0;
else{
int q=Next[p][c-'a'];
if(len[p]+1==len[q]) link[now]=q;
else{
int clone=++sz;
len[clone]=len[p]+1;
memcpy(Next[clone],Next[q],sizeof(Next[q]));
link[clone]=link[q];
while(~p&&Next[p][c-'a']==q){
Next[p][c-'a']=clone;
p=link[p];
}
link[q]=link[now]=clone;
}
}
last=now;
}
vector<int> g[maxn];
int dfs(int x){ //预处理出每一个节点表示串的出现次数
for(int i:g[x])
dp[x]+=dfs(i);
return dp[x];
}
bool vis[maxn];
int sum[maxn];
int dfs2(int x){ //预处理出每一个节点其子树的贡献和
if(vis[x]) return sum[x];vis[x]=1;
for(int i=0;i<26;++i){
int o=Next[x][i];
if(o) sum[x]+=dfs2(o);
}
return sum[x];
}
void out(int x,int k){
if(dp[x]>=k) return; //扣除该节点的贡献
k-=dp[x];
for(int i=0;i<26;++i){
int o=Next[x][i];
if(!o) continue;
else if(sum[o]<k) k-=sum[o];//如果子节点的贡献比当前k小,那么扣除子节点的贡献
else{ //如果大于,说明当前节点或者当前节点的子树包括了第k小
printf("%c",'a'+i);
out(o,k);
return ;
}
}
}
void solve(int t,int k){
for(int i=1;i<=sz;++i)
g[link[i]].push_back(i);
dfs(0);
for(int i=0;i<=sz;++i)
sum[i]=t?dp[i]:(dp[i]=1);
dp[0]=sum[0]=0;
if(dfs2(0)<k) return puts("-1"),void();
out(0,k);
}
}
char s[maxn];
int main() {
scanf("%s",s);
int t,k;scanf("%d%d",&t,&k);
sam::init();
int n=strlen(s);
for(int i=0;i<n;++i) sam::insert(s[i]);
sam::solve(t,k);
return 0;
}
CAD加油!欢迎跟我一起讨论学习算法,QQ:1401650042