题目
点这里看题目。
分析
由于这个问题与子串相关,那么我们就先把后缀自动机给建出来。
题目条件非常特殊——“出现至少两次”。而 fail 树上一个状态的祖先状态,根据定义,至少会在当前状态中出现一次。我们便可以知道,答案所对应的状态在 fail 树上一定呈祖孙关系。
我们可以用倍增法,求出状态 (S) 的祖先上第一个在 (S) 中出现两次以上的状态:先倍增找到最后一个只出现一次的串,它的父亲即是我们所求。我们称这个状态为 (pre(S)) 。
按照这个关系我们就可以设计 DP :
(f(u)):以(u)作为末状态的字符串序列的最大长度。
转移显然:
[f(u)=1+max_{pre(v)=u}f(v)
]
问题转化为了,如何判断祖先状态有没有出现多次。
我们 使用一个暴力的方法——先用线段树合并得到每个点上的 (end-pos) 集合,再进行检查。
对于状态 (S) ,它有祖先状态 (S') 。由于 (S) 在原串出现一次, (S') 就必然会跟着出现,我们就可以只针对某一个特殊的出现位置检查——比如, (S) 的第一个出现位置(fir(S))。
接着, (S') 在 (S) 中出现两次以上等价于:
[exists pin end-pos(S'),pin[fir(S)-len(S)+len(S'),fir(S))
]
即存在 (S') 的出现位置,满足它不属于 (end-pos(S)) ,但包含在了从 (fir(S)) 开始的 (S) 串里面。这个便是在 (end-pos) 上的区间查询问题,可以根据已有线段树信息解决。
综合以上方法,求 (pre) 总时间 (O(nlog_2^2n)) , DP 时间 (O(n)) 。总时间 (O(nlog_2^2n)) 。
代码
#include <cmath>
#include <cstdio>
#include <cstring>
const int MAXN = 4e5 + 5, MAXLOG = 20, MAXS = MAXN * 50;
template<typename _T>
void read( _T &x )
{
x = 0;char s = getchar();int f = 1;
while( s > '9' || s < '0' ){if( s == '-' ) f = -1; s = getchar();}
while( s >= '0' && s <= '9' ){x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar();}
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ){ putchar( '-' ); x = ( ~ x ) + 1; }
if( 9 < x ){ write( x / 10 ); }
putchar( x % 10 + '0' );
}
template<typename _T>
_T MAX( const _T a, const _T b )
{
return a > b ? a : b;
}
template<typename _T>
_T MIN( const _T a, const _T b )
{
return a < b ? a : b;
}
struct edge
{
int to, nxt;
}Graph[MAXN << 1];
int f[MAXN][MAXLOG];
bool s[MAXS]; int lch[MAXS], rch[MAXS];
int dp[MAXN], seq[MAXN], ID;
int ch[MAXN][26], fa[MAXN], mx[MAXN], head[MAXN], rot[MAXN], fir[MAXN];
int N, lg2, rt, lst, tot, cnt, siz;
char S[MAXN];
void upt( const int x ) { s[x] = s[lch[x]] | s[rch[x]]; }
void copy( int a, int b ) { fa[a] = fa[b], mx[a] = mx[b], memcpy( ch[a], ch[b], sizeof ch[b] ); }
void addEdge( const int from, const int to )
{
Graph[++ cnt].to = to, Graph[cnt].nxt = head[from];
head[from] = cnt;
}
void expand( const char c )
{
int x = c - 'a', p = lst, cur = ++ tot;
mx[cur] = mx[lst] + 1, lst = cur;
while( p && ! ch[p][x] ) ch[p][x] = cur, p = fa[p];
if( ! p ) { fa[cur] = rt; return ; }
int q = ch[p][x];
if( mx[q] == mx[p] + 1 ) { fa[cur] = q; return ; }
int nq = ++ tot; copy( nq, q );
mx[nq] = mx[p] + 1, fa[q] = fa[cur] = nq;
while( p && ch[p][x] == q ) ch[p][x] = nq, p = fa[p];
}
void update( int &u, const int l, const int r, const int pos )
{
if( ! u ) u = ++ siz;
if( l == r ) { s[u] = true; return ; }
int mid = l + r >> 1;
if( pos <= mid ) update( lch[u], l, mid, pos );
else update( rch[u], mid + 1, r, pos );
upt( u );
}
bool query( const int u, const int l, const int r, const int segL, const int segR )
{
if( ! u ) return false;
if( segL <= l && r <= segR ) return s[u];
int mid = l + r >> 1, ret = false;
if( segL <= mid ) ret |= query( lch[u], l, mid, segL, segR );
if( mid < segR ) ret |= query( rch[u], mid + 1, r, segL, segR );
return ret;
}
int merg( int u, int v )
{
if( ! u || ! v ) return u + v;
int cur = ++ siz;
lch[cur] = merg( lch[u], lch[v] );
rch[cur] = merg( rch[u], rch[v] );
s[cur] = s[u] | s[v];
return cur;
}
void DFS( const int u )
{
seq[++ ID] = u;
for( int i = head[u], v ; i ; i = Graph[i].nxt )
{
DFS( v = Graph[i].to );
rot[u] = merg( rot[u], rot[v] );
fir[u] = MIN( fir[u], fir[v] );
}
}
int main()
{
rt = lst = ++ tot;
memset( fir, 0x3f, sizeof fir );
read( N ), scanf( "%s", S + 1 );
for( int i = 1 ; i <= N ; i ++ )
expand( S[i] ),
update( rot[lst], 1, N, i ), fir[lst] = i;
for( int i = 2 ; i <= tot ; i ++ ) addEdge( fa[i], i );
DFS( rt );
lg2 = log2( tot );
for( int i = 1 ; i <= tot ; i ++ ) f[i][0] = fa[i];
for( int j = 1 ; j <= lg2 ; j ++ )
for( int i = 1 ; i <= tot ; i ++ )
f[i][j] = f[f[i][j - 1]][j - 1];
for( int i = tot, u, p ; i ; i -- )
{
u = p = seq[i];
for( int k = lg2 ; ~ k ; k -- )
if( f[p][k] > rt && ! query( rot[f[p][k]], 1, N, fir[u] - mx[u] + mx[f[p][k]], fir[u] - 1 ) )
p = f[p][k];
p = fa[p];
dp[p] = MAX( dp[p], dp[u] + 1 );
}
write( dp[rt] ), putchar( '
' );
return 0;
}