「UOJ700」可爱多的字符串

题目

有一次机灵鬼和学长可爱多打比赛, 可爱多不会做一道字符串题,机灵鬼做了很久终于做出来了,这是机灵鬼第一次做出可爱多不会的题。

可爱多觉得很丢人,于是准备研究字符串。可爱多精通 \(\mathrm{kmp}\) 算法。\(\mathrm{kmp}\) 算法的输入是一个字符串 \(S\),该算法的核心是对每个 \(i\in [1,n]\) 求出 \(\mathrm{next}_i\),定义为:

\[\begin{equation} \mathrm{next}_i = \max\{x\mid 0 \le x < i, S[1,x]=S[i-x+1,i]\} \end{equation} \]

其中 \(S[l, r]\) 表示字符串 \(S\) 中第 \(l\) 个到第 \(r\) 个字符组成的子串,如果 \(r < l\) 则为空串。

他发现,如果 \(i\)\(\mathrm{next}_i\) 连边,最后会形成一棵以 0 为根的树。他还注意到,如果一个串的“循环度”比较高的话,这颗树所有点的深度和就会比较大,比如 \(\mathrm{aaaa}\) 的树的深度和是 \(1+2+3+4\)\(\mathrm{abab}\) 的深度和为 \(1+1+2+2\),而 \(\mathrm{abcd}\) 的深度和只有 \(1+1+1+1\),他给这个树的深度和取了一个名字,叫做 \(\mathrm{next}\) 树深度和。所以,可爱多遇到一个串就想算出他的 \(\mathrm{next_i}\) 树深度和。

可爱多觉得仅仅算出一个串的 \(\mathrm{next_i}\) 树深度和并不能体现出他高超的水平。于是,他给每个位置设置了一个喜爱度 \(w_i\),现在他想计算 \(\sum_{i=1}^n w_i\times \mathrm{dep}_i\),我们称之为带权 \(\mathrm{next}_i\) 树深度和。可爱多经过很多练习过后,对带权 \(\mathrm{next_i}\) 树深度和了如指掌,如果你告诉他一个长为 \(n\) 的字符串 \(S\),以及一个长为 \(n\) 的数组 \(W=\{w_1,w_2,\dots,w_n\}\),他可以立马告诉你这个串的带权 \(\mathrm{next_i}\) 树深度。

现在机灵鬼给了可爱多一个长为 \(n\) 的字符串 \(S\) 供他研究,可爱多也设置好了 \(n\) 个位置喜爱度。机灵鬼会多次取出字符串的一部分,即多次给出 \(l,r\) ,他想让可爱多告诉他,当 \(S'=S[l,r]\)\(W'=\{w_l,w_{l+1},\dots,w_r\}\) 时,带权 \(\mathrm{next}\) 树深度和。为了避免答案过大,答案对 \(2^{32}\) 取模。

机灵鬼不想太为难可爱多,于是他给出的字符串字符集为 \(\{0,1\}\)。可爱多算不出来就会觉得很丢脸,于是请你帮帮他。


记询问次数为 \(m\)

所有数据满足 \(1\le n,m\le 2\times 10^5,1\le w_i\le 10^9\)

分析

这个题和 「CF1098F」Ж-function 有关系(就是 \(w_i=1\) 的 case),所以我们可以从中得到启发,考虑计算后缀 \(S[l,n]\)\(S[j,n]\)\(l\le j\le r\))之间产生的贡献。

具体来说,设 \(\operatorname{LCP}(i,j)\)\(S[i,n]\)\(S[j,n]\) 的最长公共前缀长度,\(s_i=\sum_{j=1}^iw_j\),则可以得到一个询问的答案为:

\[\sum_{l\le j\le r}s_{\min\{r,j+\operatorname{LCP}(l,j)-1\}}-s_{j-1} \]

\(\operatorname{LCP}\) 可以在后缀树上用 LCA 的相关信息刻画,故可以考虑在后缀树上做重链剖分,并对于每一条重链,计算 LCA 在该重链上时的答案。为了方便,之后我们仅考虑 \(\sum_{l<j\le r}s_{\min\{r,j+\operatorname{LCP}(l,j)-1\}}\) 这一块非平凡的贡献。

\(S[i,n]\) 在后缀树上对应的结点为 \(p_i\)。枚举一条重链,设结点 \(u\) 的祖先中第一个重链上的结点为 \(c_u\)\(c_u\) 所包含的最长字符串长度为 \(d_u\)。暴力一点,先枚举一下 \(c_{p_l}\)\(c_{p_j}\) 的深度关系:

  • \(c_{p_j}\) 更浅,则相当于计算 \(\sum_{l<j\le r}s_{\min\{r,j+d_j-1\}}\),此时 \(\min\) 可以通过比大小拆开。

    总的来说变成了一个三维偏序,但是因为总共有 \(O(n\log n)\) 个点和 \(O(m\log n)\) 次询问,所以复杂度是 \(O((n+m)\log^3n)\),有点夸张。

  • \(c_{p_l}\) 更浅,则相当于计算 \(\sum_{l<j\le r}s_{\min\{r,j+d_l-1\}}\)。欸,这个时候再来拆 \(\min\) 的话,则下标里会出现 \(j+d_l-1\),没法分开,怎么办?难道要做动态卷积?

    那就说明这个方法行不通。注意到此时我们可以将 \(c_j\) 限制在 \(c_l\) 的重儿子子树内,而根据后缀树的含义,这样的 \(j\) 并不平凡——它们标定了一些“出现位置”。我们进一步想到,可以将 \(s_{j+d_l-1}\) 和字符串 \(S[j,j+d_l]\) 结合起来,也即是 \(S[j,j+d_l]\) 的出现位置的右端点下标减一的 \(s\) 之和。这个问题恰好可以放在正串的 parent tree 上做,只需要定位并子树数点即可,复杂度为 \(O((n+q)\log^2n)\)

    Note.

    注意不是 \(S[j,j+d_l-1]\),因为它的出现位置不太能和重儿子中的后缀对应起来。而 \(S[j,j+d_l]\) 本身就是重儿子结点中包含的点。


    Remark.

    这里其实是对于“左端点统计”和“右端点统计”进行了一个替换。下标的平移及固定的平移长度促使我们将 \(s_{j+d_l-1}\) 看作是一个和右端点 \(j+d_l\) 有关的信息。

这样就得到了一个 \(O((n+q)\log^3n)\) 的做法,可以通过 80pts。


考虑进一步优化上面的算法,它慢就慢在第一个部分的处理中。

进一步打开 \(\min\{r,j+\operatorname{LCP}(l,j)-1\}\),可以发现其为 \(\min\{r,j+d_j-1,j+d_l-1\}\)。刚刚我们就是先讨论 \(d_j\)\(d_l\) 的大小,但注意到 \(r\)\(j+d_l-1\) 关联更加紧密,我们不妨就先将它们讨论掉。

如果 \(r\le j+d_l-1\),也即是 \(r-d_l<j\),我们要求的就是 \(\sum_{\max\{r-d_l,l\}<j\le r}s_{\min\{r,j+d_j-1\}}\)。此时因为偏序的合并,问题就只剩下了二维偏序,可以以 \(O((n+q)\log^2n)\) 的总复杂度解决;而如果 \(r-d_l\ge j\),我们要求的就是 \(\sum_{l<j\le r-d_l}s_{j-1+\min\{d_l,d_j\}}\),就可以用上面的第二部分的讨论解决了。

这里需要注意减去 \(l\) 所在子树的额外贡献(主要出现在 \(r-d_l<j\) 的贡献中)。

这样,复杂度就被优化到了 \(O((n+q)\log^2n)\)


几个小细节:

  • 在正串 parent tree 上做统计的时候,用离线扫描线配合 BIT 的方法会远比线段树合并快,值得注意。

  • 字符集为 \(\{0,1\}\),说明后缀树上一个结点只有至多两个儿子。这说明我们不用考虑一个结点的多个轻儿子之间的交叉贡献

代码

不出意料地达到了 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;
}
posted @ 2023-06-12 21:41  crashed  阅读(51)  评论(0编辑  收藏  举报