Codeforces 1207 G. Indie Album

题意:

n n n次操作,对于第 i i i个操作由下列两种方式的一种给出

  1. 1 1 1 c c c ,该字符串只含一个字符 c c c
  2. 2 2 2 x x x c c c,该字符串为第 x ( 1 ≤ x ≤ i ) x(1≤x≤i) x1xi个字符串末尾添加一个字符 c c c得到
    然后有Q次询问,每次询问给出一个字符串 s s s和位置编号 x x x,问在第 x x x个字符串中,字符串 s s s出现了几次
题解:

A C AC AC自动机,树状数组/线段树, d f s dfs dfs

首先我们可以将询问变成离线,对于所有询问的字符串建立AC自动机,同时建出fail树,然后利用fail树的性质:一个字符串在母串中出现的次数为母串在AC自动机上跑一遍并将走到的位置权值+1,该字符串所对应的fail结点的子树权值和。

现在我们的思路就比较清晰了:

在建出的AC自动机进行dfs,每走到一个结点,将所有以S为后缀的字符串的出现次数+1

如果经过的这个点是我们要求的字符串的尾结点,那么我们直接统计子树和即可

记得回溯的时候出现次数-1

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 4e5+50;
const int INF = 0x3f3f3f3f;
int nxt[MAXN][26],fail[MAXN],fa[MAXN];
char str[MAXN],ss[MAXN];
int head[MAXN],res[MAXN];
vector<pair<int,int> > ans[MAXN];
vector<int> g[MAXN];
struct node{
    int v,nxt;
}edge[MAXN<<1];
int tot,num,itot;
int sz[MAXN],dfn[MAXN],tree[MAXN];
inline void add(int u,int v){
    edge[++tot].v = v,edge[tot].nxt = head[u],head[u] = tot;
}
inline int Insert(char *s){
    int root=0,len=strlen(s);
    for(int i=0;i<len;i++){
        if(!nxt[root][s[i]-'a']) nxt[root][s[i]-'a'] = ++itot;
        root = nxt[root][s[i]-'a'];
    }
    return root;
}
inline void Build(){
    queue<int> que;
    for(int i=0;i<26;i++)
        if(nxt[0][i])
            que.push(nxt[0][i]);
    while(!que.empty()){
        int u = que.front(); que.pop();
        g[fail[u]].push_back(u);
        for(int i=0;i<26;i++){
            if(nxt[u][i]) fail[nxt[u][i]] = nxt[fail[u]][i],que.push(nxt[u][i]);
            else nxt[u][i] = nxt[fail[u]][i];
        }
    }
}
void dfs(int u){
    dfn[u]=++num,sz[u]=1;
    for(int i=0;i<(int)g[u].size();i++){
        int v = g[u][i];
        dfs(v);
        sz[u] += sz[v];
    }
}
inline int lowbit(int x) { return x&-x; }
inline void update(int x,int val){
    for(int i=x;i<=num;i+=lowbit(i)) tree[i] += val;
}
inline int query(int x){
    int sum = 0;
    for(int i=x;i;i-=lowbit(i)) sum += tree[i];
    return sum;
}
void dfs(int u,int p){
    p = nxt[p][str[u]-'a'];
    update(dfn[p],1);
    for(int i=head[u];i;i=edge[i].nxt){
        int v = edge[i].v;
        dfs(v,p);
    }
    for(int i=0;i<(int)ans[u].size();i++){
        int x = ans[u][i].first,id = ans[u][i].second;
        res[id] = query(dfn[x]+sz[x]-1)-query(dfn[x]-1);
    }
    update(dfn[p],-1);
}
int main(){
    int n; scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int op,k; scanf("%d",&op);
        if(op==2) scanf("%d",&k),fa[i]=k;
        add(fa[i],i);
        scanf("%s",&str[i]);
    }
    int m; scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int id; scanf("%d%s",&id,ss);
        int k = Insert(ss);
        ans[id].push_back(make_pair(k,i));
    }
    Build();
    dfs(0);
    for(int i=1;i<=n;i++)
        if(!fa[i])
            dfs(i,0);
    for(int i=1;i<=m;i++) printf("%d\n",res[i]);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值