「UOJ700」可爱多的字符串
题目
有一次机灵鬼和学长可爱多打比赛, 可爱多不会做一道字符串题,机灵鬼做了很久终于做出来了,这是机灵鬼第一次做出可爱多不会的题。
可爱多觉得很丢人,于是准备研究字符串。可爱多精通 算法。 算法的输入是一个字符串 ,该算法的核心是对每个 求出 ,定义为:
其中 表示字符串 中第 个到第 个字符组成的子串,如果 则为空串。
他发现,如果 向 连边,最后会形成一棵以 0 为根的树。他还注意到,如果一个串的“循环度”比较高的话,这颗树所有点的深度和就会比较大,比如 的树的深度和是 , 的深度和为 ,而 的深度和只有 ,他给这个树的深度和取了一个名字,叫做 树深度和。所以,可爱多遇到一个串就想算出他的 树深度和。
可爱多觉得仅仅算出一个串的 树深度和并不能体现出他高超的水平。于是,他给每个位置设置了一个喜爱度 ,现在他想计算 ,我们称之为带权 树深度和。可爱多经过很多练习过后,对带权 树深度和了如指掌,如果你告诉他一个长为 的字符串 ,以及一个长为 的数组 ,他可以立马告诉你这个串的带权 树深度。
现在机灵鬼给了可爱多一个长为 的字符串 供他研究,可爱多也设置好了 个位置喜爱度。机灵鬼会多次取出字符串的一部分,即多次给出 ,他想让可爱多告诉他,当 , 时,带权 树深度和。为了避免答案过大,答案对 取模。
机灵鬼不想太为难可爱多,于是他给出的字符串字符集为 。可爱多算不出来就会觉得很丢脸,于是请你帮帮他。
记询问次数为 。
所有数据满足 。
分析
这个题和 「CF1098F」Ж-function 有关系(就是 的 case),所以我们可以从中得到启发,考虑计算后缀 和 ()之间产生的贡献。
具体来说,设 为 和 的最长公共前缀长度,,则可以得到一个询问的答案为:
可以在后缀树上用 LCA 的相关信息刻画,故可以考虑在后缀树上做重链剖分,并对于每一条重链,计算 LCA 在该重链上时的答案。为了方便,之后我们仅考虑 这一块非平凡的贡献。
设 在后缀树上对应的结点为 。枚举一条重链,设结点 的祖先中第一个重链上的结点为 , 所包含的最长字符串长度为 。暴力一点,先枚举一下 和 的深度关系:
-
若 更浅,则相当于计算 ,此时 可以通过比大小拆开。
总的来说变成了一个三维偏序,但是因为总共有 个点和 次询问,所以复杂度是 ,有点夸张。
-
若 更浅,则相当于计算 。欸,这个时候再来拆 的话,则下标里会出现 ,没法分开,怎么办?
难道要做动态卷积?那就说明这个方法行不通。注意到此时我们可以将 限制在 的重儿子子树内,而根据后缀树的含义,这样的 并不平凡——它们标定了一些“出现位置”。我们进一步想到,可以将 和字符串 结合起来,也即是 的出现位置的右端点下标减一的 之和。这个问题恰好可以放在正串的 parent tree 上做,只需要定位并子树数点即可,复杂度为 。
Note.
注意不是 ,因为它的出现位置不太能和重儿子中的后缀对应起来。而 本身就是重儿子结点中包含的点。
Remark.
这里其实是对于“左端点统计”和“右端点统计”进行了一个替换。下标的平移及固定的平移长度促使我们将 看作是一个和右端点 有关的信息。
这样就得到了一个 的做法,可以通过 80pts。
考虑进一步优化上面的算法,它慢就慢在第一个部分的处理中。
进一步打开 ,可以发现其为 。刚刚我们就是先讨论 和 的大小,但注意到 和 关联更加紧密,我们不妨就先将它们讨论掉。
如果 ,也即是 ,我们要求的就是 。此时因为偏序的合并,问题就只剩下了二维偏序,可以以 的总复杂度解决;而如果 ,我们要求的就是 ,就可以用上面的第二部分的讨论解决了。
这里需要注意减去 所在子树的额外贡献(主要出现在 的贡献中)。
这样,复杂度就被优化到了 。
几个小细节:
-
在正串 parent tree 上做统计的时候,用离线扫描线配合 BIT 的方法会远比线段树合并快,值得注意。
-
字符集为 ,说明后缀树上一个结点只有至多两个儿子。这说明我们不用考虑一个结点的多个轻儿子之间的交叉贡献。
代码
不出意料地达到了 10 KB 之长。
#include <bits/stdc++.h>
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
const int MAXN = 4e5 + 5, MAXLOG = 20;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( s < '0' || '9' < s ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
struct SAM {
int ance[MAXN][MAXLOG];
int seq[MAXN], buc[MAXN];
int corr[MAXN], edp[MAXN];
int ch[MAXN][2], fa[MAXN], mx[MAXN];
int rt, lst, ntot, lg2;
SAM(): ch{{}}, fa{}, mx{}, rt( 0 ), lst( 0 ), ntot( 0 ) {}
inline void Copy( const int &a, const int &b ) {
fa[a] = fa[b], mx[a] = mx[b];
memcpy( ch[a], ch[b], sizeof( ch[b] ) );
}
inline void Expand( const char &c ) {
int x = c ^ '0', p = lst, cur = ++ ntot;
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 = ++ ntot; Copy( nq, q );
mx[nq] = mx[p] + 1, fa[cur] = fa[q] = nq;
while( p && ch[p][x] == q ) ch[p][x] = nq, p = fa[p];
}
inline void Build( const char *str ) {
rt = lst = ntot = 1;
int n = strlen( str + 1 );
rep( i, 1, n ) Expand( str[i] ), edp[corr[i] = lst] = i;
rep( i, 1, ntot ) buc[mx[i]] ++;
rep( i, 2, n ) buc[i] += buc[i - 1];
per( i, ntot, 1 ) seq[buc[mx[i]] --] = i;
rep( i, 1, ntot ) ance[i][0] = fa[i];
lg2 = log2( ntot );
rep( j, 1, lg2 ) rep( i, 1, ntot )
ance[i][j] = ance[ance[i][j - 1]][j - 1];
}
inline int Locate( const int &l, const int &r ) const {
int p = corr[r], len = r - l + 1;
for( int k = lg2 ; ~ k ; k -- )
if( ance[p][k] && mx[ance[p][k]] >= len )
p = ance[p][k];
return p;
}
};
struct BIT {
unsigned su[MAXN]; int n;
BIT(): su{}, n( 0 ) {}
inline void Init( const int &N ) {
n = N, memset( su, 0, ( n + 1 ) << 2 );
}
inline void Down( int &x ) const { x &= x - 1; }
inline void Up( int &x ) const { x += x & ( - x ); }
inline void Update( int x, unsigned v ) { for( ; x <= n ; Up( x ) ) su[x] += v; }
inline unsigned Query( int x ) const { unsigned ret = 0; for( ; x ; Down( x ) ) ret += su[x]; return ret; }
inline unsigned Query( const int &l, const int &r ) const { return Query( r ) - Query( l - 1 ); }
};
BIT su0, su1;
int qL[MAXN], qR[MAXN];
unsigned ans[MAXN];
unsigned wei[MAXN], pref[MAXN], ppref[MAXN];
char str[MAXN];
int N, Q;
namespace Forward {
struct Question {
int l, r, id;
unsigned sgn;
Question(): l( 0 ), r( 0 ), id( 0 ), sgn( 0 ) {}
Question( int L, int R, int I, unsigned S ): l( L ), r( R ), id( I ), sgn( S ) {}
};
std :: vector<Question> qst[MAXN];
std :: vector<int> son[MAXN];
int siz[MAXN], ord[MAXN], seq[MAXN], tot = 0;
SAM forw;
int ntot;
void DFS( const int &u ) {
siz[u] = 1, seq[ord[u] = ++ tot] = u;
for( const int &v : son[u] )
DFS( v ), siz[u] += siz[v];
}
inline void Init() {
forw.Build( str ), ntot = forw.ntot;
rep( i, 2, ntot ) son[forw.fa[i]].push_back( i );
DFS( 1 );
}
inline void AddQuestion( const int &u, const int &l, const int &r, const int &id ) {
qst[ord[u]].emplace_back( l, r, id, 1u );
qst[ord[u] + siz[u]].emplace_back( l, r, id, -1u );
}
inline void Work() {
su1.Init( ntot );
per( i, ntot, 1 ) {
int u = seq[i];
if( forw.edp[u] )
su1.Update( forw.edp[u], pref[forw.edp[u] - 1] );
for( const Question &q : qst[i] )
ans[q.id] += q.sgn * su1.Query( q.l, q.r );
}
}
}
namespace Backward {
std :: vector<int> assoEdp[MAXN], assoQry[MAXN], glbEdp, glbQry;
std :: vector<int> son[MAXN], qry[MAXN];
std :: vector<int> chn[MAXN];
int app[MAXN], its[MAXN], itsQ[MAXN];
int siz[MAXN], heavy[MAXN];
int tot = 0;
int mx[MAXN], edp[MAXN];
SAM bacw;
int ntot;
void DFS1( const int &u ) {
siz[u] = 1, heavy[u] = 0;
for( const int &v : son[u] ) {
DFS1( v ), siz[u] += siz[v];
if( siz[heavy[u]] < siz[v] ) heavy[u] = v;
}
app[u] = edp[u] ? edp[u] : app[son[u][0]];
}
void DFS2( const int &u ) {
for( const int &v : son[u] ) DFS2( v );
if( heavy[bacw.fa[u]] ^ u ) {
tot ++;
for( int x = u ; x ; x = heavy[x] )
chn[tot].push_back( x );
}
}
inline void Init() {
std :: reverse( str + 1, str + 1 + N );
bacw.Build( str ), ntot = bacw.ntot;
std :: reverse( str + 1, str + 1 + N );
rep( i, 2, ntot ) son[bacw.fa[i]].push_back( i );
memcpy( mx, bacw. mx, ( ntot + 1 ) << 2 );
rep( i, 1, ntot )
edp[i] = bacw.edp[i] ? N - bacw.edp[i] + 1 : 0;
DFS1( 1 ), DFS2( 1 );
}
void Collect( const int &u, std :: vector<int> &retEdp, std :: vector<int> &retQry ) {
if( edp[u] ) {
retEdp.push_back( edp[u] );
for( const int &q : qry[u] ) retQry.push_back( q );
}
}
void CollectSub( const int &u, std :: vector<int> &retEdp, std :: vector<int> &retQry ) {
Collect( u, retEdp, retQry );
for( const int &v : son[u] ) CollectSub( v, retEdp, retQry );
}
inline void Work() {
su0.Init( ntot );
su1.Init( ntot );
rep( i, 1, Q )
qry[bacw.corr[N - qL[i] + 1]].push_back( i );
rep( i, 1, tot ) {
int n = chn[i].size();
glbEdp.clear(), glbQry.clear();
rep( j, 0, n - 1 ) {
int u = chn[i][j];
std :: vector<int> &curQry = assoQry[j],
&curEdp = assoEdp[j];
curQry.clear(), curEdp.clear();
Collect( u, curEdp, curQry );
for( const int &v : son[u] )
if( v ^ heavy[u] ) {
int oQ = curQry.size(), oE = curEdp.size();
CollectSub( v, curEdp, curQry );
int nQ = curQry.size(), nE = curEdp.size();
if( oQ < nQ ) {
rep( k, oE, nE - 1 ) su0.Update( curEdp[k], 1u );
rep( k, oQ, nQ - 1 ) {
int q = curQry[k];
ans[q] -= su0.Query( std :: max( qL[q], qR[q] - bacw.mx[u] ) + 1, qR[q] ) * pref[qR[q]];
}
rep( k, oE, nE - 1 ) su0.Update( curEdp[k], -1u );
}
}
for( const int &e : curEdp ) its[e] = mx[u];
for( const int &q : curQry ) {
itsQ[q] = mx[u]; int w = heavy[u];
if( qL[q] < qR[q] - mx[u] )
Forward :: AddQuestion( Forward :: forw.Locate( app[w], app[w] + mx[u] ), qL[q] + mx[u] + 1, qR[q], q );
}
if( edp[u] ) {
for( const int &q : curQry )
if( qL[q] < edp[u] && edp[u] <= qR[q] - mx[u] )
ans[q] += pref[std :: min( qR[q], edp[u] + mx[u] - 1 )];
}
glbEdp.insert( glbEdp.end(), curEdp.begin(), curEdp.end() );
glbQry.insert( glbQry.end(), curQry.begin(), curQry.end() );
}
if( glbQry.empty() ) continue;
std :: sort( glbQry.begin(), glbQry.end(),
[] ( const int &a, const int &b ) -> bool {
return qR[a] < qR[b];
} );
std :: sort( glbEdp.begin(), glbEdp.end(),
[] ( const int &a, const int &b ) -> bool {
return a + its[a] < b + its[b];
} );
for( int m = glbEdp.size(), q = glbQry.size(), j = 0, k = 0 ; j < m || k < q ; ) {
int u = j < m ? glbEdp[j] : -1, v = k < q ? glbQry[k] : -1;
if( j < m && ( k >= q || u + its[u] <= qR[v] ) )
su0.Update( u, 1u ), su1.Update( u, pref[u + its[u] - 1] ), j ++;
else
ans[v] += su1.Query( std :: max( qL[v], qR[v] - itsQ[v] ) + 1, qR[v] ), k ++;
}
for( int m = glbEdp.size(), q = glbQry.size(), j = 0, k = 0 ; j < m || k < q ; ) {
int u = j < m ? glbEdp[j] : -1, v = k < q ? glbQry[k] : -1;
if( j < m && ( k >= q || u + its[u] <= qR[v] ) )
su0.Update( u, -1u ), su1.Update( u, -pref[u + its[u] - 1] ), j ++;
else
ans[v] += su0.Query( std :: max( qL[v], qR[v] - itsQ[v] ) + 1, qR[v] ) * pref[qR[v]], k ++;
}
// note that one node can own at most one light son only.
// thus no contribution across light subtrees needs considering.
rep( j, 0, n - 1 ) {
int u = chn[i][j];
for( const int &q : assoQry[j] )
if( qL[q] < qR[q] - mx[u] )
ans[q] += su1.Query( qL[q] + 1, qR[q] - mx[u] );
for( const int &e : assoEdp[j] )
su1.Update( e, pref[e + its[e] - 1] );
}
for( const int &e : glbEdp )
su1.Update( e, - pref[e + its[e] - 1] );
}
}
}
int main() {
Read( N ), Read( Q );
scanf( "%s", str + 1 );
rep( i, 1, N ) {
Read( wei[i] );
pref[i] = pref[i - 1] + wei[i];
ppref[i] = ppref[i - 1] + pref[i];
}
rep( i, 1, Q ) {
Read( qL[i] ), Read( qR[i] );
ans[i] = pref[qR[i]] - ( ppref[qR[i] - 1] - ( qL[i] > 1 ? ppref[qL[i] - 2] : 0 ) );
}
Forward :: Init();
Backward :: Init();
Backward :: Work();
Forward :: Work();
rep( i, 1, Q ) Write( ans[i] ), putchar( '\n' );
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
2020-06-12 [noi.ac省选模拟赛]第10场题解集合