<更新提示>
<第一次更新>
<正文>
Cool Slogans
Description
给定一个字符串 (S),要求构造字符串序列 (s_1,s_2,ldots,s_k),满足任意 (s_i) 都是 (S) 的子串,且任意 (iin[2,n]),都有 (s_i) 在 (s_{i-1}) 中出现了至少 $2$ 次(可以有重叠部分,只要起始、结尾位置不同即可)。
求可能的最大的 (k) 的值(即序列的最大可能长度)。
Input Format
第一行一个正整数$n$表示字符串长度。
第二行一行一个字符串$S$。
Output Format
一行一个正整数表示最大的$k$值。
Sample Input
11
abracadabra
Sample Output
3
解析
看到子串,考虑先建立后缀自动机。
不难发现,答案一定可以表示为$mathrm(树上一条竖直的链,因为)mathrm$树上的父子关系就是后缀关系,从一个串中取子串肯定可以精简为取一个后缀。
那就考虑在$mathrm$树上$dp$,$f_i$表示从根节点到节点$i$这条链的所有节点构成的序列的最长长度,显然是从祖先向当前节点转移。根据子串的性质,当然是越深的祖先转移过来越好,也就是能转移就转移即可。
那么现在的问题就是如何判断是否能够转移了。假设要判定$fa$是否可以转移到$u$,那么在$u$代表字符串的每一个出现位置都应该出现了至少两次$fa$代表的字符串。因为$fa$代表的字符串是$u$的一个后缀,所以一定有一次出现在了$pos_(,那么只要判定在区间)[pos_u-mathrm+mathrm,pos_u-1]$内是否出现过$fa$代表的字符串就可以了。
判定方法是用线段树合并求出每一个节点的具体$mathrm$集合,然后直接区间查询一下就可以了。
但是我们想起之前学的线段树合并好像不能保留合并前的线段树,很容易想到一个方法就是可持久化一下,只需要把新的点开出来就可以保留旧的线段树了,时空复杂度仍然是对的,就是空间常数大了一点。
(Code:)
#include <bits/stdc++.h>
using namespace std;
const int N = 400020 , Maxlog = 25;
struct SuffixAutomaton
{
int trans[N][26],link[N],maxlen[N],tot,last;
int buc[N],ord[N],id[N];
SuffixAutomaton () { last = tot = 1; }
inline void Extend(int c,int pos)
{
int cur = ++tot , p;
maxlen[cur] = maxlen[last] + 1 , id[cur] = pos;
for ( p = last; p && !trans[p][c]; p = link[p] )
trans[p][c] = cur;
if ( p == 0 ) link[cur] = 1;
else {
int q = trans[p][c];
if ( maxlen[q] == maxlen[p] + 1 ) link[cur] = q;
else {
int cl = ++tot;
maxlen[cl] = maxlen[p] + 1 , id[cl] = id[q];
memcpy( trans[cl] , trans[q] , sizeof trans[q] );
while ( p && trans[p][c] == q )
trans[p][c] = cl , p = link[p];
link[cl] = link[q] , link[q] = link[cur] = cl;
}
}
last = cur;
}
inline void Topsort(int n)
{
for (int i = 1; i <= tot; i++) ++buc[ maxlen[i] ];
for (int i = 1; i <= n; i++) buc[i] += buc[i-1];
for (int i = 1; i <= tot; i++) ord[ buc[maxlen[i]]-- ] = i;
}
};
struct SegmentTree
{
struct node { int ls,rs; };
node ver[N*Maxlog]; int tot;
#define ls(p) ver[p].ls
#define rs(p) ver[p].rs
#define mid ( l + r >> 1 )
inline void Insert(int &p,int l,int r,int v)
{
if ( !p ) p = ++tot; if ( l == r ) return void();
if ( v <= mid ) Insert( ls(p) , l , mid , v );
if ( v > mid ) Insert( rs(p) , mid+1 , r , v );
}
inline int Merge(int p,int q)
{
if ( !p || !q ) return p|q;
int cur = ++tot;
ls(cur) = Merge( ls(p) , ls(q) );
rs(cur) = Merge( rs(p) , rs(q) );
return cur;
}
inline int Query(int p,int l,int r,int ql,int qr)
{
if ( p == 0 ) return false; int res = 0;
if ( ql <= l && r <= qr ) return true;
if ( ql <= mid && ls(p) )
res |= Query( ls(p) , l , mid , ql , qr );
if ( qr > mid && rs(p) )
res |= Query( rs(p) , mid+1 , r , ql , qr );
return res;
}
};
SuffixAutomaton T; SegmentTree S;
char s[N]; int n,f[N],top[N],root[N],Ans(1);
int main(void)
{
scanf( "%d" , &n );
scanf( "%s" , s+1 );
for (int i = 1; i <= n; i++)
T.Extend( s[i] - 'a' , i ) , S.Insert( root[T.last] , 1 , n , i );
T.Topsort(n);
for (int i = T.tot; i >= 2; i--)
{
int u = T.ord[i] , Fa = T.link[u];
root[Fa] = S.Merge( root[Fa] , root[u] );
}
for (int i = 2; i <= T.tot; i++)
{
int u = T.ord[i] , Fa = T.link[u];
if ( Fa == 1 ) { f[u] = 1 , top[u] = u; continue; }
int ql = T.id[u] - T.maxlen[u] + T.maxlen[top[Fa]] , qr = T.id[u] - 1;
if ( S.Query( root[ top[Fa] ] , 1 , n , ql , qr ) )
f[u] = f[ top[Fa] ] + 1 , top[u] = u;
else f[u] = f[ top[Fa] ] , top[u] = top[Fa];
Ans = max( Ans , f[u] );
}
printf( "%d
" , Ans );
return 0;
}
<后记>