禁止的前缀

解题思路

这道题目要求我们维护两个字符串集合X和Y,并实时计算Y中没有以X中任何字符串为前缀的字符串数量。核心思路是使用字典树(Trie)来高效处理字符串前缀问题:

  1. 字典树结构:用于存储所有字符串,快速查询前缀关系

  2. 两种操作

    • 操作1(插入X):标记X中的字符串路径,并更新受影响Y字符串的计数

    • 操作2(插入Y):检查是否已有X的前缀,若无则增加计数

  3. 实时维护:每次操作后立即输出当前满足条件的Y字符串数量

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;

// 字典树相关变量
int t[N][26]; // 字典树结构,每个节点有26个子节点(a-z)
int cnt[N];    // 记录以该节点结尾的Y字符串数量
int id;        // 当前字典树节点ID
int vis[N];    // 标记是否为X中某个字符串的结束节点
int ans;       // 当前满足条件的Y字符串总数

// 操作1:将字符串s插入X集合
void ins1(string s) {
    int p = 0; // 从根节点开始
    for(int i = 0; i < s.size(); i++) {
        int x = s[i] - 'a'; // 当前字符对应的索引
        if(t[p][x] == 0) t[p][x] = ++id; // 创建新节点
        p = t[p][x]; // 移动到子节点
        if(vis[p]) break; // 如果已标记为X的前缀,提前终止
    }
    
    // 如果当前路径未被标记为X的前缀
    if(!vis[p]) {
        vis[p] = 1; // 标记为X的前缀
        int d = cnt[p]; // 获取该节点下Y字符串的数量
        int q = 0; // 重新从根节点开始
        ans -= d; // 这些Y字符串不再满足条件
        
        // 更新路径上的计数
        for(int i = 0; i < s.size(); i++) {
            int x = s[i] - 'a';
            cnt[q] -= d; // 减少路径上的计数
            q = t[q][x]; // 移动到子节点
        }
    }
}

// 操作2:将字符串s插入Y集合
void ins2(string s) {
    int p = 0;
    for(int i = 0; i < s.size(); i++) {
        int x = s[i] - 'a';
        if(t[p][x] == 0) t[p][x] = ++id;
        p = t[p][x];
        if(vis[p]) break; // 如果遇到X的前缀,提前终止
    }
    
    // 如果当前字符串没有X的前缀
    if(!vis[p]) {
        ans++; // 满足条件的Y字符串+1
        cnt[p]++; // 记录在该节点结束的Y字符串数量
        int q = 0; // 从根节点开始
        
        // 更新路径上的计数
        for(int i = 0; i < s.size(); i++) {
            int x = s[i] - 'a';
            cnt[q]++; // 增加路径上的计数
            q = t[q][x];
        }
    }
}

int main() {
    int n; cin >> n; // 查询数量
    while(n--) {
        int op; // 操作类型
        string s; // 操作字符串
        cin >> op >> s;
        if(op == 1) ins1(s); // 插入X
        else ins2(s); // 插入Y
        cout << ans << endl; // 输出当前结果
    }
    return 0;
}

 

posted @ 2025-04-28 19:02  CRt0729  阅读(11)  评论(0)    收藏  举报