洛谷P2414 [NOI2011] 阿狸的打字机 题解

哈哈,哈哈哈,哈哈哈哈 前前后后学了三天才彻底搞明白这题到底咋做,我是真的服了我自己了,菜的一

首先感谢一下luogu@yybyyb 大佬的题解,一步一步让我搞懂了AC自动机的原理。
其实基本的思路这篇题解已经写的非常详细了,这里就我自己的理解加一些方便理解的图片,同时也帮我自己消化消化这个题目。

首先有几个预备知识:
1.Tire(字典树)
这里放上百度百科,还是比较基础的数据结构。
2.KMP算法和AC自动机
这里不做太多说明,这里其实AC自动机也只是用到了一个模板,自行百度。
3.fail树
首先我们知道,如果当前节点为x,那么fail[x]即为当前节点在Tire树内最长的后缀字符串
那么反推,既然fail[x]所代表的字符串是x所代表的字符串的一个最长后缀字符串,所以我们得出结论,fail[x]所代表的字符串必定是x的一个子串(1)
在这里插入图片描述
随便举个例子,这里的fail[x]代表的字符串(bc),是x代表的字符串(abc)的一个子串。

让我们先忘记上面的部分
给出定义,当我们剔去所有原来的边,只留下fail组成的边,我们就会得到一棵fail树(2)

那fail树长到底什么样子捏? 就拿题目举例子,如下。
在这里插入图片描述
好,那么我们既然知道了fail节点指向的字符串是原串当中的一个子串(1),以及fail树(2),那我们即可得出一个结论,即为

设{ y| y ∈ 1…n }为fail[x]所代表的子串,对于节点x,y即为所有其他串在x串中的子串。
对于某个特定的y[i],我们可以知道,x的fail访问到了y[i]几次,就是,y[i]出现在x中的次数。

是不是觉得绕的一B,来画个图稍微解释一下

在这里插入图片描述
在里ababa里面,ba出现了两次,所以ababa的fail数组访问到了ba也就是8号点。

综上所述,我们可以知道,若要串x在串y中出现了几次,只需要将y的所有节点全部标1,然后找到能访问了多少次x节点,即为单次查询的答案,但很显然,单次查询的复杂度为O(m * n),所以考虑离线查询,按照y节点来查询。

那么大概思路有了,我们要怎么实现查询呢,这是一个大问题?
首先,我们要将fail树的所有边翻转一下,如下图
在这里插入图片描述
然后我们就可以观察到,如果我有一个串x,那么x如果指向某个节点y,x就一定包含在y内。
所以问题就转化为,找到某个串x往下搜索,这些搜到的串y必定包含x;那不就简单了,直接dfs过去就完事了。标记一个low数组和dfn数组,即为某一个串的所有子树能搜到的节点。

我们再用树状数组(建议去看一下树状数组)维护一下,然后我们用树状数组修改单点的值,那么我们就能直接知道,如果我访问到一个串,这个串包含了哪些子串以及这些子串都出现了几次。

好了直接上代码

// last try
#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;

int tot, nd[N], n, dep, h[N];
int low[N], c[N], dfn[N], teg = 1;
int l[N], r[N], ans[N];

struct node {
    int fail, vis[26], Vis[26];
    int fa, lt = 0;
}p[N];

struct Edge {
    int ed, next;
}e[N>>1];

struct Question {
    int x, y;
    int id, res;
}q[N];

int lowbit ( int x ) {
    return x&(-x);
}

void Modify ( int x, int w ) {
    while( x <= dep ){
        c[x] += w;
        x += lowbit(x);
    }
}

int getsum ( int x ) {
    int ret = 0;
    while ( x ) {
        ret += c[x];
        x -= lowbit(x);
    }
    return ret;
}

bool comp ( Question q1, Question q2 ) {
    return q1.y < q2.y;
}

void add( int u, int v ) {
    e[teg].ed = v;
    e[teg].next = h[u];
    h[u] = teg ++;
}

void build () {
    queue <int> Q;
    for ( int i = 0; i < 26; i ++ ) {
        if ( p[0].vis[i] )
            Q.push( p[0].vis[i] );
    }

    while ( !Q.empty() ) {

        int u = Q.front();
        Q.pop();

        for ( int i = 0; i < 26; i ++ ) {
            int tu = p[u].vis[i];

            if ( tu ) {
                p[tu].fail = p[p[u].fail].vis[i];
                Q.push(tu);
            }
            else {
                p[u].vis[i] = p[p[u].fail].vis[i];
            }
        }
    }
}

void dfs ( int u ) {
    dfn[u] = ++ dep;
    for ( int i = h[u]; i; i = e[i].next ) {
        dfs( e[i].ed );
    }
    low[u] = dep;
}

void Dfs ( int u ) {
    Modify( dfn[u], 1 );

    if ( p[u].lt ) {
        int ty = p[u].lt;
        for ( int i = l[ty]; i <= r[ty]; i ++ ) {
            int tx = nd[q[i].x];
            q[i].res = getsum( low[tx] ) - getsum( dfn[tx] - 1 );
        }
    }

    for ( int i = 0; i < 26; i ++ ) {
        if ( p[u].Vis[i] ) {
            Dfs( p[u].Vis[i] );
        }
    }
    Modify( dfn[u], -1 );
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    string ss; cin >> ss;

    memset( r, -1, sizeof r );

    int now = 0;
    for ( int i = 0; i < ss.length(); i ++ ) {
        if ( ss[i] <= 'z' && ss[i] >= 'a' ) {
            int val = ss[i] - 'a';
            if ( !p[now].vis[val] ) {
                p[now].vis[val] = ++tot;
                p[tot].fa = now;
            }
            now = p[now].vis[val];
        }
        if ( ss[i] == 'B' ) now = p[now].fa;
        if ( ss[i] == 'P' ) {
            p[now].lt = ++n;
            nd[n] = now;
        }
    }

    for ( int i = 0; i <= tot; i ++ ) {
        for( int j = 0; j < 26; j ++ ) {
            p[i].Vis[j] = p[i].vis[j];
        }
    }

    build();

    for ( int i = 1; i <= tot; i ++ ) {
        add( p[i].fail, i );
    }

    dfs (0);

    int Q; cin >> Q;
    for ( int i = 1; i <= Q; i ++ ) {
        cin >> q[i].x >> q[i].y;
        q[i].id = i;
    }

    sort ( q + 1, q + 1 + Q, comp );

    for ( int i = 1; i <= Q; i ++ ) {
        int z = q[i].y;
        if ( !l[z] ) l[z] = i;
        r[z] = i;
    }

    Dfs( 0 );

    for ( int i = 1; i <= Q; i ++ ) ans[q[i].id] = q[i].res;
    for ( int i = 1; i <= Q; i ++ ) cout << ans[i] << endl;
}

结束,因为没学好树状数组,导致我一直看不懂题解,卡了两天才想明白… …
这里面dfn和low数组直接找到串的子树也是比较巧妙,还是值得学习的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值