当前位置:首页 > 技术 > 正文内容

基于哈希与字典树的字符串匹配验证算法

访客 技术 2026年7月5日 1

本题要求验证一棵树中从根到任意节点的路径所形成的序列,是否都能在给定的字符串集合中找到匹配项。这里的"匹配"是指该路径序列是某个给定字符串的前缀。

解决方法有两种主要思路:

方案一:字符串哈希 + DFS 验证

首先对输入的 n 个字符串逐个计算其所有前缀的哈希值,并记录在哈希表中。然后构建树结构并以深度优先搜索的方式遍历整棵树,在遍历过程中不断累积当前路径上的哈希值。如果某条路径对应的哈希值未出现在预处理好的哈希表中,则说明不满足条件,直接返回失败结果。

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

typedef unsigned long long ull;
const int MAXN = 2e5 + 7;
const ull BASE = 2333;

unordered_map<ull, bool> hashTable;
int nodeValue[MAXN], head[MAXN], edgeCount;
struct Edge {
    int to, next;
} edges[MAXN * 2];

void addEdge(int u, int v) {
    edges[edgeCount] = {v, head[u]};
    head[u] = edgeCount++;
    edges[edgeCount] = {u, head[v]};
    head[v] = edgeCount++;
}

bool dfsCheck(int currentNode, int parent, ull currentHash) {
    for (int i = head[currentNode]; ~i; i = edges[i].next) {
        int nextNode = edges[i].to;
        if (nextNode == parent) continue;
        ull newHash = currentHash * BASE + nodeValue[nextNode];
        if (!hashTable[newHash]) return false;
        if (!dfsCheck(nextNode, currentNode, newHash)) return false;
    }
    return true;
}

int main() {
    int n, m;
    scanf("%d%d", &n, &m);

    memset(head, -1, sizeof(head));

    ull prefixHash;
    int length, value;
    for (int i = 0; i < n; ++i) {
        scanf("%d", &length);
        prefixHash = 0;
        for (int j = 0; j < length; ++j) {
            scanf("%d", &value);
            prefixHash = prefixHash * BASE + value;
            hashTable[prefixHash] = true;
        }
    }

    for (int i = 1; i <= m; ++i) {
        scanf("%d", &nodeValue[i]);
    }

    int u, v;
    for (int i = 1; i < m; ++i) {
        scanf("%d%d", &u, &v);
        addEdge(u, v);
    }

    ull rootHash = nodeValue[1];
    if (!hashTable[rootHash]) {
        printf("NO\n");
    } else {
        if (dfsCheck(1, 0, rootHash)) {
            printf("YES\n");
        } else {
            printf("NO\n");
        }
    }

    return 0;
}

方案二:字典树(Trie)实现方式

另一种更直观的做法是使用字典树来存储所有目标字符串的前缀信息。对于每一个输入的字符串,将其所有字符依次插入到字典树中。之后再通过DFS遍历整棵树,每一步都检查当前节点的值是否存在字典树中的对应子节点,若不存在则表示无法匹配。

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

const int MAXN = 2e5 + 7;

int nodeValues[MAXN];
vector<int> tree[MAXN];
map<int, int> trieNodes[MAXN];
int nodeCount = 1;

void insertString(const vector<int>& str) {
    int curNode = 0;
    for (int ch : str) {
        if (trieNodes[curNode].find(ch) == trieNodes[curNode].end()) {
            trieNodes[nodeCount].clear();
            trieNodes[curNode][ch] = nodeCount++;
        }
        curNode = trieNodes[curNode][ch];
    }
}

bool traverseTree(int curTreeNode, int parent, int curTrieNode) {
    bool result = true;
    for (int child : tree[curTreeNode]) {
        if (child == parent) continue;
        auto it = trieNodes[curTrieNode].find(nodeValues[child]);
        if (it == trieNodes[curTrieNode].end()) return false;
        result &= traverseTree(child, curTreeNode, it->second);
    }
    return result;
}

int main() {
    int n, m;
    scanf("%d%d", &n, &m);

    vector<int> tempStr;
    int len, val;
    for (int i = 0; i < n; ++i) {
        scanf("%d", &len);
        tempStr.clear();
        for (int j = 0; j < len; ++j) {
            scanf("%d", &val);
            tempStr.push_back(val);
        }
        insertString(tempStr);
    }

    for (int i = 1; i <= m; ++i) {
        scanf("%d", &nodeValues[i]);
    }

    int x, y;
    for (int i = 1; i < m; ++i) {
        scanf("%d%d", &x, &y);
        tree[x].push_back(y);
        tree[y].push_back(x);
    }

    if (trieNodes[0].find(nodeValues[1]) == trieNodes[0].end()) {
        printf("NO\n");
    } else {
        int startTrieNode = trieNodes[0][nodeValues[1]];
        if (traverseTree(1, 0, startTrieNode)) {
            printf("YES\n");
        } else {
            printf("NO\n");
        }
    }

    return 0;
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。