[BZOJ3881]-[Coci2015]Divljak-AC自动机+fail树+dfs序前缀和

说在前面

这两天写了好几道trie和AC自动机上各种瞎搞的题目,感觉自己码力+++++
1A了很开心呢,不过慢成doge….还以为会T掉hhhhh


题目

BZOJ3881传送门

题面

Alice有 n 个字符串S1,S2Sn,Bob有一个字符串集合 T ,一开始集合是空的。
接下来会发生q个操作,操作有两种形式:
「1 P」,Bob往自己的集合里添加了一个字符串P。
「2 x」,Alice询问Bob,集合T中有多少个字符串包含串 Sx 。(我们称串A包含串B,当且仅当B是A的子串)
Bob遇到了困难,需要你的帮助。

输入输出格式

输入格式:
第1行,一个数n
接下来n行,每行一个字符串表示 Si
下一行,一个数q
接下来q行,每行一个操作,格式见题目描述。

输出格式:
对于每一个Alice的询问,帮Bob输出答案


解法

(直接看的题解了hhhh)
用log复杂度维护信息,再log复杂度通过信息得出答案的方式来解这道题

首先对于所有的 Si 建出AC自动机,然后把fail树搞出来。
对于一个新加进来的字符串 P <script type="math/tex" id="MathJax-Element-920">P</script>,在AC自动机上跑一遍,每到一个节点打上一个vis标记。很明显,如果一个「末尾节点」或者「通过fail可以指向该末尾节点的节点」被vis了,那么这个末尾节点所代表的串就被覆盖了,而这些节点在fail树中都存在于末尾节点的子树内。

也就是说,现在问题转化成了「在一棵树上统计子树标记种类数」。那么对于同一种标记,将所有有当前点的标记按照dfs序排序,然后每个点的位置+1,相邻点LCA的位置-1,查询子树和即可。这一步可以用树状数组维护,正确性可以自己YY一下

(关于正确性:u,v的位置+1,那么对于LCA(u,v)上面的串相当于加了2,所以在LCA上-1。如果这时候w也要+1,但是实际上需要+1的位置只有w到LCA(w,v)这一小段,也就是在w位置+1,然后LCA(w,v)-1,同理推广)


下面是自带大常数的代码

这个代码是 真·自带大常数

/**************************************************************
    Problem: 3881
    User: Izumihanako
    Language: C++
    Result: Accepted
    Time:15976 ms
    Memory:437872 kb
****************************************************************/
 
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
 
int N , Q , head[2000005] , tp , AC_cnt , endId[2000005] ;
char ss[2000005] ;
struct AC_Node ;
struct Path ;
 
struct AC_Node{
    int isend , id ;
    char c ;
    AC_Node *fail , *ch[26] ;
}*root ;
struct Path{
    int pre , to ;
}p[4000005] ;
struct BIT{
    int b[2000005] ;
    void init(){
        memset( b , 0 , ( AC_cnt + 1 ) * sizeof( int ) ) ;
    }
    void add( int x , int delta ){
        for( ; x <= AC_cnt ; x += x&-x )
            b[x] += delta ;
    }
    int query( int x ){
        int rt = 0 ;
        for( ; x ; x -= x&-x ) rt += b[x] ;
        return rt ;
    }
}B ;
 
void In( int t1 , int t2 ){
    p[++tp].pre = head[t1] ;
    p[ head[t1] = tp ].to = t2 ;
}
 
int in[2000005] , out[2000005] , dfs_c ;
int fa[21][2000005] , dep[2000005] ;
void dfs( int u ){
    in[u] = ++dfs_c ;
    for( int i = head[u] ; i ; i = p[i].pre ){
        int v = p[i].to ;
        fa[0][v] = u ;
        dep[v] = dep[u] + 1 ;
    //  printf( "(dfs )%d to %d\n" , u , v ) ;
        dfs( v ) ;
    }
    out[u] = dfs_c ;
}
 
void setST(){
    for( int i = 1 ; i <= 20 ; i ++ )
        for( int j = 1 ; j <= AC_cnt ; j ++ )
            fa[i][j] = fa[i-1][ fa[i-1][j] ] ;
}
 
int Lca( int u , int v ){
    if( dep[u] < dep[v] ) swap( u , v ) ;
    int t = dep[u] - dep[v] , x = 0 ;
    while( t ){
        if( t&1 ) u = fa[x][u] ;
        t >>= 1 ; x ++ ;
    }
    if( u == v ) return u ;
    for( int i = 20 ; i >= 0 ; i -- )
        if( fa[i][u] != fa[i][v] )
            u = fa[i][u] , v = fa[i][v] ;
    return fa[0][u] ;
}
 
void newNode( AC_Node *&nd ){
    ++ AC_cnt ;
    nd = new AC_Node() ;
    nd->isend = 0 ;
    nd->fail = NULL ;
    nd->id = AC_cnt ;
    memset( nd->ch , 0 , sizeof( nd->ch ) ) ;
}
 
void Insert( char *ts , int idnum ){
    int len = strlen( ts ) ;
    AC_Node *nd = root ;
    for( int i = 0 ; i < len ; i ++ ){
        int nxt = ts[i] - 'a' ;
        if( !nd->ch[nxt] ){
            newNode( nd->ch[nxt] ) ;
            nd->ch[nxt]->c = ts[i] ;
        }
        nd = nd->ch[nxt] ;
    }
    nd->isend = idnum ;
    endId[idnum] = nd->id ;
}
 
queue<AC_Node*> que ;
void getFail(){
    que.push( root ) ;
    while( !que.empty() ){
        AC_Node *u = que.front() ; que.pop() ;
        for( int i = 0 ; i < 26 ; i ++ ){
            if( !u->ch[i] ) continue ;
            AC_Node *p = u->fail , *v = u->ch[i] ;
            while( p && !p->ch[i] ) p = p->fail ;
            v->fail = ( p ? p->ch[i] : root ) ;
            que.push( v ) ;
            //单向边!!!
            In( v->fail->id , v->id ) ;
        }
    }
}
 
int sta[2000005] , topp ;
void Run( char *ts ){
    topp = 0 ;
    int len = strlen( ts ) ;
    AC_Node *nd = root ;
    for( int i = 0 ; i < len ; i ++ ){
        int nxt = ts[i] - 'a' ;
        while( nd != root && !nd->ch[nxt] )
            nd = nd->fail ;
        if( nd->ch[nxt] ){
            nd = nd->ch[nxt] ;
            sta[++topp] = nd->id ;
        }
    }
}
 
bool cmp( const int &a , const int &b ){
    return in[a] < in[b] ;
}
 
void solve(){
    getFail() ;
    fa[0][1] = 1 ;
    dfs( 1 ) ;
    setST() ;
    B.init() ;
 
    scanf( "%d" , &Q ) ;
    for( int i = 1 , opt , x ; i <= Q ; i ++ ){
        scanf( "%d" , &opt ) ;
        if( opt == 1 ){
            scanf( "%s" , ss ) ;
            Run( ss ) ;
            sort( sta + 1 , sta + topp + 1 , cmp ) ;
        //  for( int j = 1 ; j <= topp ; j ++ )
        //      printf( "Passed( %d )\n" , sta[j] ) ;
            for( int j = 1 ; j <= topp ; j ++ )
                B.add( in[ sta[j] ] , 1 ) ;
            for( int j = 2 ; j <= topp ; j ++ ){
                B.add( in[ Lca(sta[j],sta[j-1]) ] , -1 ) ;
        //      printf( "Lca : %d\n" , Lca( sta[j] , sta[j-1] ) ) ;
            }
        } else{
            scanf( "%d" , &x ) ;
            int rg = B.query( out[ endId[x] ] ) ,
                lf = B.query( in[ endId[x] ] - 1 ) ;
            printf( "%d\n" , rg - lf ) ;
        }
    }
}
 
int main(){
    newNode( root ) ; root->c = 0 ;
    scanf( "%d" , &N ) ;
    for( int i = 1 ; i <= N ; i ++ ){
        scanf( "%s" , ss ) ;
        Insert( ss , i ) ;
    }
    solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值