[BZOJ2754]-[SCOI2012]喵星球上的点名-AC自动机+树状数组

本文介绍使用AC自动机解决字符串匹配问题的方法,包括如何构建AC自动机、查询字符串包含关系及统计被包含次数等,并提供了一段示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

说在前面

感觉这题还是挺经典的
所以还是写了记录一下…


题目

BZOJ2754传送门
洛谷P2336传送门

看题可进传送门
题目…略长,概括起来有点麻烦


解法

读完这道题之后,可以发现实际上它就是要我们求两个东西
「一个串被多少个串包含」和「一个串包含了多少串」

对于第一个问题:
一个串A被另一个串B包含,假设我们建出了A和B的AC自动机,那么说明从B的某个节点开始跳fail,最后可以跳到A的end。
如果只建A的AC自动机,也可以达到类似的效果。将B放在A的AC自动机上跑一遍,如果某一个被经过的节点,可以跳fail跳到A的end,那么就说明B包含了A。

于是只需要在fail树中查询:A的end的子树中是否有点被路过。即可知道B是否包含了A

那么现在是要统计「一个串被多少个串包含」,一个串只能算一次,于是可以这么干:先把所有经过的节点按照在fail树中的dfs序排序,然后这些点对应的位置打上+1标记,相邻点LCA的位置打上-1的标记。这样的话,一个串在一个子树中只会被统计一次。

对于第二个问题:
沿用AC自动机的思想,如果B包含了A,那么B的节点跳fail会跳到A。那么B一共包含了多少个串呢?就是「B所有节点的fail链的并」上有多少个end节点。统计方法和第一个问题类似,对于每个节点先求出该节点到root路径上有多少个end,把这个记为end_cnt 。答案就是:B的所有节点的end_cnt减去相邻点lca的end_cnt

实现的话,需要支持单点修改,区间查询,树状数组就好了


下面是自带大常数的代码

#include <map>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

vector<int> fir[20005] , sec[20005] ;
int N , M , ss[100005] , id_c , head[100005] , tp , global_ID ;
struct Node{
    int end , id ;
    Node *fail , *fa ;
    map<int,Node*> ch ;
} *root , w[100005] , *tw = w ;
struct Path{
    int pre , to ;
}p[200005] ;
struct BIT{
    int b[100005] ;
    void Add( int x ){ for( ; x <= id_c ; x += x&-x ) ++ b[x] ; }
    void Del( int x ){ for( ; x <= id_c ; x += x&-x ) -- b[x] ; }
    int Query( int x ){
        int rt = 0 ;
        for( ; x ; x -= x&-x ) rt += b[x] ;
        return rt ;
    } int Query( int L , int R ){ return Query( R ) - Query( L - 1 ) ; }
}B ;

void In( int t1 , int t2 ){
    p[++tp] = ( Path ){ head[t1] , t2 } ; head[t1] = tp ;
}

void newNode( Node *&nd , Node *fa ){
    nd = ++ tw ; nd->id = ++id_c ;
    nd->ch.clear() ; nd->end = 0 ;
    nd->fail = NULL ; nd->fa = fa ;
}

int ssID[100005] ;
void Insert( int *ss , int len , int ith ){
    Node *nd = root ;
    for( int i = 0 ; i < len ; i ++ ){
        int id = ss[i] ;
        if( !nd->ch.count( id ) ) newNode( nd->ch[id] , nd ) ;
        nd = nd->ch[id] ;
    } nd->end ++ ; ssID[ith] = nd->id ;
}

void buildAC(){
    int fr = 1 , ba = 0 ;
    Node *que[100005] ; que[++ba] = root ;
    while( ba >= fr ){
        Node *u = que[fr++] ;
        for( map<int,Node*>::iterator it = u->ch.begin() ; it != u->ch.end() ; it ++ ){
            Node *v = it->second , *p = u->fail ;
            int id = it->first ;
            while( p && !p->ch.count( id ) ) p = p->fail ;
            v->fail = ( p ? p->ch[id] : root ) ;
            In( v->fail->id , v->id ) ;
            v->end += v->fail->end ;
            que[++ba] = v ;
        }
    }
}

int dep[100005] , fa[17][100005] , logg , maxd , in[100005] , out[100005] , dfs_c ;
void dfsFailTree( 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 ;
        maxd = max( maxd , dep[v] ) ;
        dfsFailTree( v ) ;
    } out[u] = dfs_c ;
}

int LCA( int u , int v ){
    bool print ;
    print = ( u == 10 && v == 8 ) ;
    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] ;
        x ++ ; t >>= 1 ;
    } if( u == v ) return u ;
    for( int i = logg ; i >= 0 ; i -- )
        if( fa[i][u] != fa[i][v] ) 
            u = fa[i][u] , v = fa[i][v] ;
    return fa[0][u] ;
}

void preWork(){
    buildAC() ;
    dfsFailTree( 1 ) ; fa[0][1] = 1 ;
    for( logg = 1 ; 1 << ( logg + 1 ) <= maxd ; logg ++ ) ;
    for( int i = 1 ; i <= logg ; i ++ )
        for( int j = 1 ; j <= id_c ; j ++ )
            fa[i][j] = fa[i-1][ fa[i-1][j] ] ;
}

int insta[100005] ;
int sta[100005] , topp ;
void Run( vector<int> &ss , int len ){
    Node *nd = root ;
    for( int i = 0 ; i < len ; i ++ ){
        int id = ss[i] ;
        while( nd && !nd->ch.count( id ) ) nd = nd->fail ;
        if( nd ){
            nd = nd->ch[id] ;
            if( insta[ nd->id ] != global_ID ){
                insta[ nd->id ] = global_ID ;
                sta[++topp] = nd->id ;
            }
        } else nd = root ;
    }
}
bool cmp( const int &A , const int &B ){ return in[A] < in[B] ; }

int nekoAns[100005] ;
void solve(){
    for( int i = 1 ; i <= N ; i ++ ){
        global_ID = i ; topp = 0 ;
        Run( fir[i] , fir[i].size() ) ;
        Run( sec[i] , sec[i].size() ) ;
        sort( sta + 1 , sta + topp + 1 , cmp ) ;
        B.Add( in[ sta[1] ] ) , nekoAns[i] += w[ sta[1] ].end ;

        for( int j = 2 ; j <= topp ; j ++ ){
            int Lca = LCA( sta[j-1] , sta[j] ) ;
            B.Del( in[ Lca ] ) , nekoAns[i] -= w[Lca].end ;
            B.Add( in[ sta[j] ] ) , nekoAns[i] += w[ sta[j] ].end ;
        }
    }
    for( int i = 1 ; i <= M ; i ++ )
        printf( "%d\n" , B.Query( in[ ssID[i] ] , out[ ssID[i] ] ) ) ;
    for( int i = 1 ; i < N ; i ++ )
        printf( "%d " , nekoAns[i] ) ;
    printf( "%d" , nekoAns[N] ) ;

}

int main(){
    newNode( root , NULL ) ;
    scanf( "%d%d" , &N , &M ) ;
    for( int i = 1 , len ; i <= N ; i ++ ){
        scanf( "%d" , &len ) ; fir[i].resize( len ) ;
        for( int j = 0 ; j < len ; j ++ ) scanf( "%d" , &fir[i][j] ) ;
        scanf( "%d" , &len ) ; sec[i].resize( len ) ;
        for( int j = 0 ; j < len ; j ++ ) scanf( "%d" , &sec[i][j] ) ;
    } for( int i = 1 , len ; i <= M ; i ++ ){
        scanf( "%d" , &len ) ;
        for( int j = 0 ; j < len ; j ++ ) scanf( "%d" , &ss[j] ) ;
        Insert( ss , len , i ) ;
    } preWork() ; solve() ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值