JZOJ4617 【NOI2016模拟7.12】可持久化字符串 建Trie后用主席树维护next数组

本文介绍了一种使用Trie树和主席树解决字符串历史版本查询问题的方法,通过维护节点间的转移关系,实现字符串更新及查找最短循环节长度。

题目大意

现在要求可持久地维护一个字符集大小为M字符串S,可以支持对历史版本的询问。现在有N个操作,对于第i个操作包含两个整数u,v,表示在第u个版本的字符串末尾加上字符v作为第i个版本的字符串,并且输出每次操作之后的字符串的最短循环节长度。强制在线。

N,M3105

解题思路

首先要知道的是对于一个字符串S,它的最小循环节的长度为最大的t,使t满足既是S的前又是S的后缀。也就相当于KMPnext数组,更形象的说一个长度为len的的串,它的最小循环节等于lennextlen

然后我们发现题目实际上要求我们维护一颗Trie,并且对于每条到根的链维护出next数组。那么复杂度肯定会有问题。那么我们考虑一样另一种方式维护这个东西。设T(x,y)表示在x到根的路径上,如果下一个字符为y那么根据next数组的定义会跳到哪个节点上,Lenx表示当前这个节点到跟所代表字符串的长度(也就是在Trie上的深度)。我们看当前要进行第i次操作,插入的字符为c,要在第u个版本后插入。那么毫无疑问Ans=LeniLenT(u,c)。那么现在的问题就是怎么维护这个T数组。

那么考虑T(i)T(T(u,c))的关系。我们设节点T(u,c)在节点i方向的字符边是c,节点是k,那么T(i)T(T(u,c))不同的就只有关于c的转移,那么就是说T(i)=T(T(u,c))T(i,c)=k。这个可以用主席树维护,那么就可以在Log的时间转移和维护。

程序

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 3e5 + 5;

struct Node {
    int l, r, Num;
} Tr[MAXN * 20];

int tot, Ans, N, M, Ord, Fa[MAXN][20], Len[MAXN], Root[MAXN], A[MAXN];

int Query(int Rt, int l, int r, int Side) {
    if (!Rt) return 0;
    if (l == r) return Tr[Rt].Num;
    int Mid = (l + r) >> 1;
    if (Side <= Mid) return Query(Tr[Rt].l, l, Mid, Side); else
        return Query(Tr[Rt].r, Mid + 1, r, Side);
}

void Modify(int &Rt, int rt, int l, int r, int Side, int Poi) {
    Rt = ++ tot;
    Tr[Rt] = Tr[rt];
    if (l == r) {
        Tr[Rt].Num = Poi;
        return;
    }
    int Mid = (l + r) >> 1;
    if (Side <= Mid) Modify(Tr[Rt].l, Tr[rt].l, l, Mid, Side, Poi); else
        Modify(Tr[Rt].r, Tr[rt].r, Mid + 1, r, Side, Poi);
}

int main() {
    scanf("%d%d%d", &N, &M, &Ord);
    for (int i = 1; i <= N; i ++) {
        int u, c;
        scanf("%d%d", &u, &c);
        if (Ord == 1) u ^= Ans, c ^= Ans;
        A[i] = c;
        Fa[i][0] = u;
        for (int j = 1; j < 20; j ++) Fa[i][j] = Fa[Fa[i][j - 1]][j - 1];
        Len[i] = Len[u] + 1;
        int Side = Query(Root[u], 1, M, c);
        Ans = Len[i] - Len[Side];
        printf("%d\n", Ans);
        int p = i, Up = Ans - 1;
        for (int j = 19; j + 1; j --)
            if (Len[i] - Len[Fa[p][j]] <= Up) p = Fa[p][j];
        Modify(Root[i], Root[Side], 1, M, A[p], p);
    }
}
### 关于NOI金牌导航中的字符串算法与后缀数组 在处理涉及多个字符串连接并构其后缀数组的问题时,一种有效的方法是在这些字符串之间插入不同的分隔符[^1]。此方法确保了即使原始字符串内部存在重复部分,在计算后缀数组过程中仍能区分来自不同源字符串的部分。 对于具体实现而言,当面对需要高效查询和匹配的任务时,除了基本的后缀数组外,还可以考虑使用更高级的数据结构如广义SAM(Suffix Automaton),这有助于统计本质不同的子串数量以及其他复杂的模式识别操作[^3]。然而值得注意的是,并不是所有情况下都适合采用最复杂的数据结构;有时简单的基于后缀数组的操作已经能够满足需求,尤其是在时间复杂度要求较为严格的情况下——例如通过二分法配合后缀分组来解决问题可以达到\(O(n\log n)\)的时间效率。 针对特定应用场景下的性能优化也至关重要。某些场景下,尽管理论上可行的做法可能因为实际输入特性而表现不佳,比如高度随机化的Height数组可能导致较高复杂度算法超时(TLE),此时就需要根据具体情况调整策略以适应数据特点[^5]。 ```cpp // 示例代码:简单展示如何创两个字符串之间的后缀数组 #include <iostream> #include <vector> #include <string> using namespace std; void build_suffix_array(const string &text, vector<int> &suffixArray){ int n = text.size(); suffixArray.resize(n); // 构造后缀数组逻辑... } int main(){ string s1="abc",s2="def"; char separator='#'; // 假设'#'不在任何给定字符串内出现 string combinedString=s1+separator+s2; vector<int> sa; build_suffix_array(combinedString,sa); cout << "Combined String with Separator: " << combinedString << endl; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值