Bingbong的回文路径

Bingbong的回文路径

题目描述

Bingbong 有一棵有根树,根节点为 1,总共有 n 个节点。树中的节点通过 n1 条无向边相连,每条边的权重为 1

树中的每个节点包含一个小写字母。Bingbong 将选择从节点 u 开始,并在选择最短路径的情况下到达节点 v。他想知道他所走路径形成的字符串是否是一个回文串。由于他会进行多次行走,您需要回答多个查询。

输入描述:

第一行包含一个整数 n(1n105),表示节点的数量。

第二行包含 n 个小写字母,其中第 i 个字母表示第 i 个节点上的小写字母。

第三行包含 n 个整数,其中第 i 个数字 ai(1aii1) 表示第 i 个节点的父节点,对于根节点 ai=0

第四行包含一个整数 q(1q105),表示查询的数量。

接下来是 q 行,每行包含两个整数 uv,表示一个查询的起点和终点。

输出描述:

输出 q 行,每行包含一个字符串。如果查询的起点到终点的路径连接形成一个回文串,则输出 "YES",否则输出 "NO"。

示例1

输入

5
bbaaa
0 1 1 2 2
3
2 3
4 3
5 3

输出

NO
YES
YES

 

解题思路

  假设 uv 的最近公共祖先是 p,从 u 走到 p 再走到 v 的字符串表示为 supv,同理从 v 走到 p 再走到 u 的字符串表示为 svpu。当 supv=svpu 时说明 uv 路径上的字符串是回文串。判断回文串可以用字符串哈希(这里基数 P13331,自然溢出取模),由于是树上的问题我们可以通过倍增来维护树链的信息。

  定义 fa[u][i] 表示从 u 往上走 2i 步后达到的节点,h1[u][i] 表示从 u 往上走 2i 步构成字符串的哈希值(不含 u 上的字符),h2[u][i] 表示从 fa[u][i] 往下走 2i 步构成字符串的哈希值(含 fa[u][i] 上的字符)。以下图为例:

  从 u 往上走 22 步构成的字符串为 acbc,从 fa[u][2] 往下走 22 步构成的字符串为 cbca

  考虑如何维护这些信息。对于 fa[u][i],我们先从 u 往上走 2i1 步到达 fa[u][i1],再从 fa[u][i1] 往上走 2i1 步走到 fa[u][i],因此有 fa[u][i]=fa[fa[u][i1]][i1]

  对于两个字符串 s1s2,哈希值分别为 h1h2,那么拼接后的 s1s2 的哈希值就是 h1P|s2|+h2。因此 h1[u][i]=h1[u][i1]P2i1+h1[fa[u][i1]][i1]h2[u][i]=h2[fa[u][i1]][i1]P2i1+h2[u][i1]

  对于询问的 (u,v),我们可以仿照求最近公共祖先的做法,分别求出 uvvu 对应字符串的哈希值。在 u 往上跳到 p 的过程中,维护 hu1 表示从 u 往上跳的过程中构成字符串的哈希值,和 hu2 表示往下跳到 u 的过程中构成字符串的哈希值。同理维护 vhv1hv2。最后让 u 跳到 pv 跳到 p 的下一个节点,判断是否满足 hu1Pd2+hv2=hv1Pd1+hu2 即可,其中 d1 表示 u 往上跳经过的节点个数,d2 表示 v 往上跳经过的节点个数。

  AC 代码如下,时间复杂度为 O(nlogn+qlogn)

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

typedef unsigned long long ULL;

const int N = 1e5 + 5, M = N * 2, P = 13331;

char s[N];
int h[N], e[M], ne[M], idx;
int fa[N][17], dep[N];
ULL h1[N][17], h2[N][17], pp[N];

void add(int u, int v) {
    e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}

void dfs(int u, int p) {
    dep[u] = dep[p] + 1;
    fa[u][0] = p;
    h1[u][0] = h2[u][0] = s[p];
    for (int i = 1; i <= 16; i++) {
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
        h1[u][i] = h1[u][i - 1] * pp[1 << i - 1] + h1[fa[u][i - 1]][i - 1];
        h2[u][i] = h2[fa[u][i - 1]][i - 1] * pp[1 << i - 1] + h2[u][i - 1];
    }
    for (int i = h[u]; i != -1; i = ne[i]) {
        int v = e[i];
        if (v == p) continue;
        dfs(v, u);
    }
}

bool query(int a, int b) {
    if (dep[a] < dep[b]) swap(a, b);
    ULL ha1 = s[a], ha2 = s[a], hb1 = s[b], hb2 = s[b];
    int d1 = 1, d2 = 1;
    for (int i = 16; i >= 0; i--) {
        if (dep[fa[a][i]] >= dep[b]) {
            ha1 = ha1 * pp[1 << i] + h1[a][i];
            ha2 = h2[a][i] * pp[d1] + ha2;
            d1 += 1 << i;
            a = fa[a][i];
        }
    }
    if (a == b) return ha1 == ha2;
    for (int i = 16; i >= 0; i--) {
        if (fa[a][i] != fa[b][i]) {
            ha1 = ha1 * pp[1 << i] + h1[a][i];
            ha2 = h2[a][i] * pp[d1] + ha2;
            d1 += 1 << i;
            a = fa[a][i];
            hb1 = hb1 * pp[1 << i] + h1[b][i];
            hb2 = h2[b][i] * pp[d2] + hb2;
            d2 += 1 << i;
            b = fa[b][i];
        }
    }
    // 当前a和b往上跳1步就会同时到达最近公共祖先,选择让a跳
    ha1 = ha1 * pp[1] + h1[a][0];
    ha2 = h2[a][0] * pp[d1++] + ha2;
    return ha1 * pp[d2] + hb2 == hb1 * pp[d1] + ha2;
}

int main() {
    int n, m;
    scanf("%d %s", &n, s + 1);
    memset(h, -1, sizeof(h));
    for (int i = 1; i <= n; i++) {
        int x;
        scanf("%d", &x);
        add(i, x), add(x, i);
    }
    pp[0] = 1;
    for (int i = 1; i <= n; i++) {
        pp[i] = pp[i - 1] * P;
    }
    dfs(1, 0);
    scanf("%d", &m);
    while (m--) {
        int a, b;
        scanf("%d %d", &a, &b);
        printf("%s\n", query(a, b) ? "YES" : "NO");
    }
    
    return 0;
}

 

参考资料

  牛客小白月赛91文字版题解:https://ac.nowcoder.com/discuss/1294108

posted @   onlyblues  阅读(44)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2023-04-20 最长公共子串
Web Analytics
点击右上角即可分享
微信分享提示