bzoj4231回忆树——AC自动机

bzoj4231 回忆树

给定一颗Trie树,每次询问u到v的路径上构成的字符串包含了给定模式串T多少次。

思路

考虑离线之后对于所有的模式串建立AC自动机,考虑跨过lca的串范围有限,直接暴力kmp即可。

然后我们就将一次询问拆成了两条链了,可以将每次询问挂在链上然后离线去dfs,每次dfs时将这个节点添加进AC自动机匹配,对于一段u到v的链,我们在链头的某一个位置减去之前不合法的匹配数量,再在链尾加上总的匹配数量就好了。

如何用ac自动机来记录某个固定的串匹配了多少次的话,可以建立fail树之后用BIT来动态维护子树和。

/*=======================================
 * Author : ylsoi
 * Time : 2019.7.2
 * Problem : bzoj4231
 * E-mail : [email protected]
 * ====================================*/
#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define DEBUG(x) cerr<<#x<<"="<<x<<" "
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define pii pair<int,int>
typedef long long ll;

using namespace std;

void File(){
    freopen("bzoj4231.in","r",stdin);
    freopen("bzoj4231.out","w",stdout);
}

template<typename T>void read(T &_){
    _=0; T f=1; char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())_=(_<<1)+(_<<3)+(c^'0');
    _*=f;
}

string proc(){
    ifstream f("/proc/self/status");
    return string(istreambuf_iterator<char>(f),istreambuf_iterator<char>());
}

const int maxn=3e5+10;
int n,m,ans[maxn];
int beg[maxn],to[maxn<<1],las[maxn<<1],cha[maxn<<1],cnte=1;

void add(int u,int v,int c){
    las[++cnte]=beg[u],beg[u]=cnte,to[cnte]=v,cha[cnte]=c;
    las[++cnte]=beg[v],beg[v]=cnte,to[cnte]=u,cha[cnte]=c;
}

namespace aca{
    int ch[maxn][26],fail[maxn],cnt;
    int dfn[maxn],sz[maxn],cnt_dfn;
    int sum[maxn];
    int lowbit(int x){return x&(-x);}
    vector<int>G[maxn];
    int insert(int len,char *t){
        int o=0,c;
        REP(i,1,len){
            c=t[i]-'a';
            if(!ch[o][c])ch[o][c]=++cnt;
            o=ch[o][c];
        }
        return o;
    }
    void get_fail(){
        queue<int>qu;
        REP(i,0,25)if(ch[0][i])qu.push(ch[0][i]);
        while(!qu.empty()){
            int u=qu.front(); qu.pop();
            REP(i,0,25){
                int v=ch[u][i];
                if(v)fail[v]=ch[fail[u]][i],qu.push(v);
                else ch[u][i]=ch[fail[u]][i];
            }
        }
        REP(i,1,cnt)G[fail[i]].push_back(i);
    }
    void dfs(int u){
        dfn[u]=++cnt_dfn,sz[u]=1;
        REP(i,0,G[u].size()-1){
            int v=G[u][i];
            dfs(v),sz[u]+=sz[v];
        }
    }
    void update(int u,int x){
        u=dfn[u];
        for(;u<=cnt_dfn;u+=lowbit(u))sum[u]+=x;
    }
    int query(int u){
        int l=dfn[u]-1,r=dfn[u]+sz[u]-1,ret=0;
        for(;l>=1;l-=lowbit(l))ret-=sum[l];
        for(;r>=1;r-=lowbit(r))ret+=sum[r];
        return ret;
    }
}

int st[maxn][21],Log[maxn],dep[maxn],fa_ch[maxn];

void dfs(int u,int fh){
    st[u][0]=fh,dep[u]=dep[fh]+1;
    for(int i=beg[u];i;i=las[i]){
        int v=to[i];
        if(v==fh)continue;
        fa_ch[v]=cha[i];
        dfs(v,u);
    }
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int d=Log[dep[x]-dep[y]];dep[x]!=dep[y];--d)
        if(dep[st[x][d]]>=dep[y])x=st[x][d];
    if(x==y)return x;
    for(int d=Log[dep[x]];d>=0;--d)
        if(st[x][d]!=st[y][d])
            x=st[x][d],y=st[y][d];
    return st[x][0];
}

int kth(int x,int k){
    if(k<0)return x;
    REP(i,0,Log[k])if(1<<i&k)x=st[x][i];
    return x;
}

int kmp(char *s,char *t,int lens,int lent){
    if(lens<lent)return 0;
    reverse(s,s+lens);
    static int fail[maxn];
    fail[0]=fail[1]=0;
    REP(i,1,lent-1){
        int j=fail[i];
        while(j && t[j]!=t[i])j=fail[j];
        if(t[j]==t[i])fail[i+1]=j+1;
        else fail[i+1]=0;
    }
    int j=0,ret=0;
    REP(i,0,lens-1){
        while(j && s[i]!=t[j])j=fail[j];
        if(s[i]==t[j])++j;
        if(j==lent)++ret;
    }
    return ret;
}

struct node{int id,o,ty;};
vector<node>qu[maxn];

void solve(int u,int o,int fh){
    using namespace aca;
    if(u!=1){
        int c=fa_ch[u];
        o=ch[o][c];
        update(o,1);
    }
    REP(i,0,qu[u].size()-1){
        ans[qu[u][i].id]+=qu[u][i].ty*query(qu[u][i].o);
    }
    for(int i=beg[u];i;i=las[i]){
        int v=to[i];
        if(v==fh)continue;
        solve(v,o,u);
    }
    if(u!=1)update(o,-1);
}

int main(){
    File();

    read(n),read(m);

    int u,v,anc;
    static char s[maxn],t[maxn];
    REP(i,2,n){
        read(u),read(v),scanf("%s",t);
        add(u,v,t[0]-'a');
    }
    dfs(1,0);
    REP(i,2,n)Log[i]=Log[i>>1]+1;
    REP(j,1,Log[n])REP(i,1,n)
        if(dep[i]-(1<<j)>=1)
            st[i][j]=st[st[i][j-1]][j-1];

    REP(i,1,m){
        read(u),read(v);
        scanf("%s",t+1);
        int len=strlen(t+1);
        anc=lca(u,v);
        int p1=aca::insert(len,t);
        reverse(t+1,t+len+1);
        int p2=aca::insert(len,t);
        int cnt=0;
        int u1=kth(u,dep[u]-dep[anc]-len+1),u2=u1;
        REP(j,1,dep[u1]-dep[anc])s[++cnt]='a'+fa_ch[u2],u2=st[u2][0];
        if(dep[u]-dep[anc]>=len){
            qu[u1].push_back((node){i,p2,-1});
            qu[u].push_back((node){i,p2,1});
        }
        int v1=kth(v,dep[v]-dep[anc]-len+1),v2=v1;
        cnt+=dep[v1]-dep[anc];
        REP(j,1,dep[v1]-dep[anc])s[cnt-j+1]='a'+fa_ch[v2],v2=st[v2][0];
        if(dep[v]-dep[anc]>=len){
            qu[v1].push_back((node){i,p1,-1});
            qu[v].push_back((node){i,p1,1});
        }
        s[cnt+1]='\0';
        ans[i]+=kmp(s+1,t+1,cnt,len);
    }

    aca::get_fail();
    aca::dfs(0);

    solve(1,0,0);

    REP(i,1,m)printf("%d\n",ans[i]);

    return 0;
}

原文地址:https://www.cnblogs.com/ylsoi/p/11122931.html

时间: 2024-11-10 20:45:11

bzoj4231回忆树——AC自动机的相关文章

BZOJ4231 : 回忆树

一个长度为$|S|$的串在树上匹配有两种情况: 1.在LCA处转弯,那么这种情况只有$O(|S|)$次,暴力提取出长度为$2|S|$的链进行KMP即可. 2.不转弯,那么可以拆成两个到根路径的询问. 对所有串的正反串建立AC自动机,求出fail树上每个点的DFS序. 然后DFS原树,记录在AC自动机上走到了哪个点,在那个点$+1$,回溯的时候$-1$. 那么一个询问的答案就是fail树上的子树和,树状数组维护即可. 时间复杂度$O(n\log n+|S|)$. #include<cstdio>

【暖*墟】 #AC自动机# 多模式串的匹配运用

一.构建步骤 1.将所有模式串构建成 Trie 树 2.对 Trie 上所有节点构建前缀指针(类似kmp中的next数组) 3.利用前缀指针对主串进行匹配 AC自动机关键点一:trie字典树的构建过程 字典树的构建过程是这样的,当要插入许多单词的时候,我们要从前往后遍历整个字符串, 当我们发现当前要插入的字符其节点再先前已经建成,我们直接去考虑下一个字符即可, 当我们发现当前要插入的字符没有再其前一个字符所形成的树下没有自己的节点, 我们就要创建一个新节点来表示这个字符,接下往下遍历其他的字符.

跳跃表,字典树(单词查找树,Trie树),后缀树,KMP算法,AC 自动机相关算法原理详细汇总

第一部分:跳跃表 本文将总结一种数据结构:跳跃表.前半部分跳跃表性质和操作的介绍直接摘自<让算法的效率跳起来--浅谈"跳跃表"的相关操作及其应用>上海市华东师范大学第二附属中学 魏冉.之后将附上跳跃表的源代码,以及本人对其的了解.难免有错误之处,希望指正,共同进步.谢谢. 跳跃表(Skip List)是1987年才诞生的一种崭新的数据结构,它在进行查找.插入.删除等操作时的期望时间复杂度均为O(logn),有着近乎替代平衡树的本领.而且最重要的一点,就是它的编程复杂度较同类

LA_3942 LA_4670 从字典树到AC自动机

首先看第一题,一道DP+字典树的题目,具体中文题意和题解见训练指南209页. 初看这题模型还很难想,看过蓝书提示之后发现,这实际上是一个标准DP题目:通过数组来储存后缀节点的出现次数.也就是用一颗字典树从后往前搜一发.最开始觉得这种搞法怕不是要炸时间,当时算成了O(N*N)毕竟1e5的数据不搞直接上N*N的大暴力...后来发现,字典树根本跑不完N因为题目限制字典树最多右100层左右. 实际上这道题旧思想和模型来说很好(因为直观地想半天还真想不出来..)但是实际实现起来很简单--撸一发字典树就好了

BZOJ 2434: [Noi2011]阿狸的打字机 [AC自动机 Fail树 树状数组 DFS序]

2434: [Noi2011]阿狸的打字机 Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2545  Solved: 1419[Submit][Status][Discuss] Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的:l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最

【uva1502/hdu4117-GRE Words】DP+线段树优化+AC自动机

这题我的代码在hdu上AC,在uva上WA. 题意:按顺序输入n个串以及它的权值di,要求在其中选取一些串,前一个必须是后一个的子串.问d值的和最大是多少. (1≤n≤2×10^4 ,串的总长度<=3*10^5) 题解: 这题一开始我的方向就错了,想了很久d[x][y]表示在AC自动机上的节点x.下一个串要大于y的dp.然而这样做数组要10^4*10^5=10^9级别,开都开不了,妥妥超时. 后来看了一眼题解...觉得自己智商真是感人... 用f[i]表示以第i个串为结尾的时候最大的d值,这样做

HDU 5384 字典树、AC自动机

题目:http://acm.hdu.edu.cn/showproblem.php?pid=5384 用字典树.AC自动机两种做法都可以做 1 #include<stdio.h> 2 #include<string.h> 3 #include<string> 4 #include<iostream> 5 using namespace std; 6 struct node{ 7 int cnt; 8 node *next[26]; 9 node(){ 10 c

BZOJ 2434: [Noi2011]阿狸的打字机( AC自动机 + DFS序 + 树状数组 )

一个串a在b中出现, 那么a是b的某些前缀的后缀, 所以搞出AC自动机, 按fail反向建树, 然后查询(x, y)就是y的子树中有多少是x的前缀. 离线, 对AC自动机DFS一遍, 用dfs序+树状数组维护, DFS到的查询点就回答询问.时间复杂度O(|ACAM|+QlogQ) ------------------------------------------------------------------------------------------- #include<cstdio>

[NOI2011]阿狸的打字机 AC自动机+DFS序+树状数组

[NOI2011]阿狸的打字机 Description 阿狸喜欢收藏各种稀奇古怪的东西,最近他淘到一台老式的打字机.打字机上只有28个按键,分别印有26个小写英文字母和'B'.'P'两个字母.经阿狸研究发现,这个打字机是这样工作的: l 输入小写字母,打字机的一个凹槽中会加入这个字母(这个字母加在凹槽的最后).l 按一下印有'B'的按键,打字机凹槽中最后一个字母会消失.l 按一下印有'P'的按键,打字机会在纸上打印出凹槽中现有的所有字母并换行,但凹槽中的字母不会消失.例如,阿狸输入aPaPBbP