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

本文通过实战案例深入浅出地讲解了AC自动机的工作原理及其应用。从Tire树、KMP算法讲起,逐步过渡到fail树的概念,并结合树状数组进行优化,最终实现了高效的字符串匹配。

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

首先感谢一下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数组直接找到串的子树也是比较巧妙,还是值得学习的

<think>好的,用户需要查找P1173 NOI2016网格问题的Python解决方案或代码实现。首先,我需要回忆一下这个题目的具体要求。题目大意是判断一个网格是否会被障碍物分成不连通的部分,这可能涉及到图论中的割点或割边判断。 接下来,我需要考虑如何用Python实现这个问题的解法。这类题目通常需要高效的算法,因为数据规模可能较大。Python在处理大数据时可能会遇到性能问题,所以需要考虑优化方法。 首先,题目中的网格可以看作是一个图,其中某些格子被障碍物占据。判断是否存在割点,即移除某个点后图是否不连通,这是关键。Tarjan算法通常用于寻找割点,但实现起来需要注意细节,尤其是用Python时递归深度可能受限,可能需要用迭代方式或者优化代码。 然后,我需要查阅是否有现有的Python解法。不过用户提到站内引用中的内容似乎与快速排序或其他问题相关,可能不直接适用。但根据引用[2],用户提到了代码优化和UB(未定义行为)的问题,这提示在编写Python代码时需要注意避免类似错误,比如确保所有分支都有返回值,避免无限循环等。 另外,用户可能需要的是具体的代码示例。考虑到Python在二维网格问题中的处理,通常会用坐标转换、邻接表或矩阵来表示图结构。对于割点检测,可以使用Tarjan算法,维护每个节点的dfn(发现时间)和low(能回溯到的最早节点)值,判断是否存在子节点无法通过其他路径回到祖先节点。 不过,Python的递归深度限制可能对Tarjan算法产生影响,尤其是在大网格情况下。因此,可能需要用非递归的DFS实现,或者调整递归深度限制。但这也可能导致代码复杂度增加。 另外,题目中的障碍物可能形成多个连通块,需要处理多个情况。比如,当障碍物数量较少时,可能直接判断是否形成包围圈;当障碍物较多时,需要更复杂的判断逻辑。 总结步骤可能包括: 1. 将网格转换为图结构,标记障碍物。 2. 使用Tarjan算法寻找割点。 3. 判断是否存在割点导致图不连通。 4. 处理特殊情况,如网格过小、障碍物数量不足等。 在编写Python代码时,需要注意数据结构的效率,比如使用集合或字典来快速访问节点,避免重复计算。此外,可能的优化包括剪枝、预处理等。 最后,根据用户提供的引用,虽然引用内容不直接相关,但需要注意代码的正确性和优化,避免出现未定义行为或逻辑错误。例如,确保所有函数都有正确的返回语句,避免因编译器优化导致的错误,这在Python中可能表现为逻辑错误而非运行错误。</think>关于 P1173 NOI2016 网格问题的 Python 解法,该问题属于图论中的连通性判断问题,需要判断是否存在障碍物形成的割点导致网格不连通。以下是解题思路和实现要点: --- ### **问题核心分析** 题目要求判断 $n \times m$ 网格中,给定 $c$ 个障碍物的位置后,是否会将网格分割为多个不连通区域。关键在于判断是否存在 **割点**(即移除该点会导致图不连通)或 **割边**。 --- ### **算法选择** 1. **Tarjan 算法**:用于寻找无向图的割点。若存在割点,则网格可能被分割。 2. **连通性检测**:需考虑障碍物分布对整体连通性的影响,特别是边界情况和稀疏障碍物场景[^1]。 --- ### **Python 实现步骤** #### 1. **坐标离散化与邻接表构建** 将网格坐标转换为图中的节点,并为每个非障碍物节点建立邻接关系: ```python def build_graph(n, m, obstacles): obstacles_set = set(obstacles) graph = defaultdict(list) directions = [(-1,0), (1,0), (0,-1), (0,1)] for x in range(n): for y in range(m): if (x, y) not in obstacles_set: for dx, dy in directions: nx, ny = x + dx, y + dy if 0 <= nx < n and 0 <= ny < m and (nx, ny) not in obstacles_set: graph[(x, y)].append((nx, ny)) return graph ``` #### 2. **Tarjan 算法实现割点检测** 通过深度优先搜索(DFS)计算每个节点的 `dfn`(发现时间)和 `low`(回溯值): ```python def find_cut_points(graph, start): visited = set() dfn, low = {}, {} parent = {} cut_points = set() time = 0 def tarjan(u): nonlocal time dfn[u] = low[u] = time time += 1 children = 0 for v in graph[u]: if v not in dfn: parent[v] = u children += 1 tarjan(v) low[u] = min(low[u], low[v]) if parent.get(u) is None and children > 1: cut_points.add(u) elif parent.get(u) is not None and low[v] >= dfn[u]: cut_points.add(u) elif v != parent.get(u): low[u] = min(low[u], dfn[v]) tarjan(start) return cut_points ``` #### 3. **特殊情况处理** - **极小网格**:如 $n \times m \leq 2$ 时无需判断。 - **障碍物数量不足**:若障碍物数量少于 2,则无法分割网格[^2]。 --- ### **完整代码框架** ```python from collections import defaultdict def main(): n, m, c = map(int, input().split()) obstacles = [tuple(map(int, input().split())) for _ in range(c)] if n * m - c < 2: print("No") return if c == 0: print("Yes" if n * m >= 2 else "No") return graph = build_graph(n, m, obstacles) nodes = list(graph.keys()) if not nodes: print("No") return cut_points = find_cut_points(graph, nodes[0]) print("Yes" if len(cut_points) > 0 else "No") if __name__ == "__main__": main() ``` --- ### **注意事项** 1. **性能优化**:Python 递归深度有限,对大规模网格需改用迭代式 DFS。 2. **边界条件**:注意坐标范围和障碍物去重。 3. **连通性验证**:需确保所有非障碍物节点均被访问,防止漏判。 --- 相关问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值