Description
Solution
省选之前打SAM练练手(虽然跟重新学没什么区别),太久没有复习了,很多关于SAM的定义理解什么的都忘光了。
这题就是建一个SAM,然后合并parent树上的endpos集合,可以直接dsu on tree.
考虑对于一个节点,它可以选择的长度为L到R,二分一个长度,那么答案就是 ∑ m i n ( Δ i , l e n ) \sum min(\Delta i,len) ∑ m i n ( Δ i , l e n ) , Δ i \Delta i Δ i 是相邻的endpos的差。
那么分类,对于 < = l e n <=len < = l e n 的维护 ∑ Δ i \sum \Delta i ∑ Δ i ,对于 > l e n >len > l e n 的维护它们的个数。可以在用set启发式合并的同时用树状数组维护。
判断字典序可以哈希找LCP(十分清真),或者从后往前建后缀树,然后给每一条fail边记录一个相邻的字符,然后按照字符的大小从小往大遍历(但是并不好看)。
然后就是两个log的了。
关于SAM
每一个点对应一个 e n d p o s endpos e n d p o s 集合,它的转移边 t o [ c ] to[c] t o [ c ] 表示在它的所有 e n d p o s endpos e n d p o s 后加上 c c c 之后对应的 e n d p o s endpos e n d p o s 集合。所以它们的 e n d p o s endpos e n d p o s 是包含关系。
新加入一个点 x x x 的时候,遍历上一个点的所有后缀,也就是不断跳fail的过程,如果当前跳的点没有c这条边,那么就直接连上,相当于是把endpos分裂了。
如果当前点已经有c这条边,那么如果len[to]=len[x]+1,那么当前的后缀加上c也别的endpos也在to[c]里面,所以只需要把fail指过去,继承新的节点的endpos就好了。
如果len[to]>len[x]+1,那么需要把to分裂成len[x]+1的部分和大于它的部分,因为由于新加进来的点的存在导致x的c边的endpos必须包括新点,但是多出来的部分的endpos与这个矛盾,所以需要分裂。
然后把fail链上指向原来未分裂的点的点的c边指向分裂点。
话说只需要不打错SAM,应该就没有什么好调试的了,还是比较好打的(主要是难以理解)。
关于后缀树
在建立SAM的时候同时对于所有新点储存它的endpos,分裂点储存任意一个endpos(也可以不储存,这里的endpos主要是为了确定fail链的字符)。
然后在连接fail的时候可以根据end顺便找到原串的某个字符,表示这条fail链的字符大小。
后缀树每一个点的endpos即为子树内endpos的并。
倒着建后缀树,两个节点的LCA即为它们的最长公共前缀。
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <set>
#define maxn 40005
using namespace std;
int n, m, i, j, k, a[ maxn] , s[ maxn] ;
struct node{
int fa, to[ 26 ] , end, len, p[ 26 ] ; } t[ maxn] ;
int las, tot, rt;
struct Treearray{
int s[ maxn] ;
void add ( int x, int d) {
for ( ; x<= n+ 1 ; x+ = x& - x) s[ x] + = d; }
int sum ( int x, int S= 0 ) {
for ( ; x> 0 ; x- = x& - x) S+ = s[ x] ; return S; }
void clr ( ) {
memset ( s, 0 , sizeof ( s) ) ; }
} T1, T2;
int newnode ( ) {
tot++ ; t[ tot] . fa= t[ tot] . end= t[ tot] . len= 0 ;
memset ( t[ tot] . to, 0 , sizeof ( t[ tot] . to) ) ;
memset ( t[ tot] . p, 0 , sizeof ( t[ tot] . p) ) ;
return tot;
}
void link ( int x, int y) {
t[ x] . fa= y;
if ( t[ y] . p[ s[ t[ x] . end- t[ y] . len] ] )
printf ( "!!!" ) ;
t[ y] . p[ s[ t[ x] . end- t[ y] . len] ] = x;
}
void del ( int x, int y) {
t[ y] . p[ s[ t[ x] . end- t[ y] . len] ] = 0 ;
}
void add ( int c, int End) {
int np= newnode ( ) , p= las; las= np;
t[ np] . end= End, t[ np] . len= t[ p] . len+ 1 ;
for ( ; p&& ! t[ p] . to[ c] ; p= t[ p] . fa) t[ p] . to[ c] = np;
if ( ! p) link ( np, rt) ; else {
int q= t[ p] . to[ c] ;
if ( t[ q] . len== t[ p] . len+ 1 ) link ( np, q) ; else {
int nq= newnode ( ) ;
t[ nq] = t[ q] , t[ nq] . len= t[ p] . len+ 1 ;
memset ( t[ nq] . p, 0 , sizeof ( t[ nq] . p) ) ;
del ( q, t[ q] . fa) ;
link ( nq, t[ q] . fa) ;
link ( np, nq) , link ( q, nq) ;
for ( ; p&& t[ p] . to[ c] == q; p= t[ p] . fa)
t[ p] . to[ c] = nq;
}
}
}
int S[ maxn] , tp, sz[ maxn] , g[ maxn] , totd, dfn[ maxn] ;
void dfs ( int x) {
sz[ x] = 1 , g[ x] = 0 , dfn[ x] = ++ totd;
for ( int i= 0 ; i< 26 ; i++ ) if ( t[ x] . p[ i] ) {
dfs ( t[ x] . p[ i] ) , sz[ x] + = sz[ t[ x] . p[ i] ] ;
if ( ! g[ x] || sz[ t[ x] . p[ i] ] > sz[ g[ x] ] )
g[ x] = t[ x] . p[ i] ;
}
}
int ansl, ansr, ansi;
set< int > id;
set< int > :: iterator it, it0, it1;
void insert ( int i) {
it= id. lower_bound ( i) ;
if ( it!= id. end ( ) && * it== i) return ;
if ( it!= id. begin ( ) && it!= id. end ( ) ) {
it0= it, it0-- ;
T1. add ( * it- * it0, - ( * it- * it0) ) ;
T2. add ( n+ 1 - ( * it- * it0) , - 1 ) ;
}
if ( it!= id. end ( ) ) {
int j= * it;
T1. add ( j- i, j- i) ;
T2. add ( n+ 1 - ( j- i) , 1 ) ;
}
if ( it!= id. begin ( ) ) {
it0= it, it0-- ;
int j= * it0;
T1. add ( i- j, i- j) ;
T2. add ( n+ 1 - ( i- j) , 1 ) ;
}
id. insert ( i) ;
}
void Del ( int i) {
it= id. lower_bound ( i) ;
if ( it== id. end ( ) || * it!= i) return ;
it0= it, it0++ ;
if ( it0!= id. end ( ) ) {
int j= * it0;
T1. add ( j- i, - ( j- i) ) ;
T2. add ( n+ 1 - ( j- i) , - 1 ) ;
if ( it!= id. begin ( ) ) {
it1= it, it1-- ;
int k= * it1;
T1. add ( j- k, j- k) ;
T2. add ( n+ 1 - ( j- k) , 1 ) ;
}
}
if ( it!= id. begin ( ) ) {
it0= it, it0-- ; int j= * it0;
T1. add ( i- j, - ( i- j) ) ;
T2. add ( n+ 1 - ( i- j) , - 1 ) ;
}
id. erase ( it) ;
}
void clear ( int x) {
for ( int i= 0 ; i< 26 ; i++ ) if ( t[ x] . p[ i] )
clear ( t[ x] . p[ i] ) ;
Del ( t[ x] . end) ;
}
void putin ( int x) {
for ( int i= 0 ; i< 26 ; i++ ) if ( t[ x] . p[ i] )
putin ( t[ x] . p[ i] ) ;
insert ( t[ x] . end) ;
}
void dfs2 ( int x) {
for ( int i= 0 ; i< 26 ; i++ ) if ( t[ x] . p[ i] != g[ x] && t[ x] . p[ i] )
dfs2 ( t[ x] . p[ i] ) , clear ( t[ x] . p[ i] ) ;
if ( g[ x] ) dfs2 ( g[ x] ) ;
for ( int i= 0 ; i< 26 ; i++ ) if ( t[ x] . p[ i] != g[ x] )
putin ( t[ x] . p[ i] ) ;
insert ( t[ x] . end) ;
int l= t[ t[ x] . fa] . len+ 1 , r= t[ x] . len, mid, p= 0 ;
while ( l<= r) {
mid= ( l+ r) >> 1 ;
int tmp= T1. sum ( mid) + T2. sum ( n+ 1 - mid- 1 ) * mid;
if ( tmp< m) l= mid+ 1 ; else
if ( tmp> m) r= mid- 1 ; else
{
p= mid; break ; }
}
if ( p) {
if ( ! ansi|| p< ansr- ansl+ 1
|| p== ansr- ansl+ 1 && ( dfn[ x] <= ansi&& ansi<= dfn[ x] + sz[ x] - 1 || ansi> dfn[ x] ) )
ansi= dfn[ x] , ansr= * ( ++ id. begin ( ) ) , ansl= ansr- p+ 1 ;
}
}
int main ( ) {
int T;
scanf ( "%d" , & T) ;
while ( T-- ) {
char ch= getchar ( ) ; while ( ch< 'a' || ch> 'z' ) ch= getchar ( ) ;
n= 0 ; while ( ch>= 'a' && ch<= 'z' ) a[ ++ n] = ch- 'a' , ch= getchar ( ) ;
tot= 0 ; las= rt= newnode ( ) ;
T1. clr ( ) , T2. clr ( ) ;
for ( i= n; i>= 1 ; i-- )
s[ n- i+ 1 ] = a[ i] , add ( a[ i] , n- i+ 1 ) ;
scanf ( "%d" , & m) ;
id. clear ( ) , id. insert ( 0 ) ;
totd= 0 , dfs ( 1 ) ;
ansl= ansr= ansi= 0 , dfs2 ( 1 ) ;
if ( ansi== 0 ) printf ( "NOTFOUND!\n" ) ; else {
for ( i= ansr; i>= ansl; i-- )
putchar ( 'a' + s[ i] ) ;
putchar ( '\n' ) ;
}
}
}