hncpc2021 J题(离线 + AC自动机)

在这里插入图片描述

题目大意:

​ 有一个字符串的集合,有以下三种操作:

​ I s :在集合中插入一个字符串 s 。

​ D s: 删除在集合中字符串中子串包含 s 的。

​ Q s:查询集合中字符串包含子串 s 的数量。

思路:

​ (本题目官方解法是广义SAM,我目前还不会,也没有想到什么好的SA解法,偶然在知乎上面看到有人说可以用AC自动机离线的做法,很快写了出来,后悔比赛的时候没深入想。。。)

​ 采用离线的思路,将所有询问串和删除串分别建立AC自动机,对于每一个插入串,找到它后面第一个出现的并能够将他删除的操作,然后找到这个区间内所有与这个插入串匹配的询问串,添加答案即可。

​ 有些细节,具体见代码。

代码

#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define ls (p<<1)
#define rs (p<<1|1)
#define mid ((l+r)>>1)
#define code(i, j) ((i - 1) * m + j)
#define all(x) (x).begin(),(x).end()
#define all1(x) (x).begin()+1,(x).end()
#define fi first
#define se second
#define ctz __builtin_ctzll
#define ppc __builtin_popcountll
#define de(x) cerr << (x) << '\n'
typedef long long LL;
typedef unsigned long long ull;
typedef double db;
const int maxn = 5e5+10;
const int maxm = 4e7+10;
const int mod = 998244353;
const int INF = 1e9;
const int dx[] = {-2, -2, -1, 1, 2, 2, -1, 1}, dy[] = {-1, 1, 2, 2, 1, -1, -2, -2};
const db PI = acosl(-1);
const db EPS = 1e-6;
const int di[]={-1, 0, 1, 0, -1};
const int dir[6][3] ={{0, -1, 0}, {0, 1, 0}, {0, 0, 1}, {0, 0, -1}, {1, 0, 0}, {-1, 0, 0}};
//----------------------------------------------------//

int ans[maxn], mi; // 答案数组和区间标记


struct AC {
    const int N = 1e6 + 10;
    vector<vector<int>> t, num;
    vector<int> fail;
    int tot = 1;
    AC () {
        num.resize(N);
        fail.resize(N);
        t.resize(N);
        for(int i = 0; i < N; i ++) {
            t[i].resize(26);
        }
    }
    void insert(string s, int id){
        int p = 1, len = s.size();
        for(int i = 0; i < len; i ++) {
            int ch = s[i] - 'a';
            if(!t[p][ch]) {
                t[p][ch] = ++ tot;
            }
            p = t[p][ch];
        }
        num[p].push_back(id);
    }
    void getfail(){
        for(int i = 0; i < 26; i ++ ) {
            t[0][i] = 1;
        }
        queue<int> q;
        q.push(1);
        fail[1] = 0;
        while(q.size()){
            int u = q.front();
            q.pop();
            for(int i = 0; i < 26; i ++){//遍历所有儿子
                int v = t[u][i];
                int Fail = fail[u];//失配结点 tire[fail].son[i] 与i相同
                if(!v) {//没有的话 通过父亲对的失配来连接
                    t[u][i] = t[Fail][i];
                    continue;
                }
                fail[v] = t[Fail][i];//存在 直接指
                q.push(v);
            }
        }
    }
    void query(string s, int l, int r, int op){
        vector<int> ed(tot + 1); // 防止重复计算
        int p = 1, len = s.size();
        for(int i = 0; i < len; i ++){
            int v = s[i]-'a';
            int k = t[p][v];//跳fail
            while(k > 1 && !ed[k]){ // 不唯一
                for(auto x : num[k]) {
                    if(op == 0) { // 删除的串
                        if(x >= l && x <= r) { // 注意范围
                            mi = min(mi, x);
                        }
                    } else { // 询问串
                        if(x >= l && x <= r) { // 注意范围
                            ans[x] ++; // 直接添加
                        }
                    }
                }
                ed[k] = 1; // 标记
                k = fail[k];
            }
            p = t[p][v];
        }
    }
};
void slove() {
    int n;
    cin >> n;

    vector<char> op(n);
    vector<string> s(n);

    AC t1, t2;

    for(int i=0;i<n;i++) {
        cin >> op[i] >> s[i];
        if(op[i] == 'Q') t1.insert(s[i], i);
        if(op[i] == 'D') t2.insert(s[i], i);
    }

    t1.getfail();
    t2.getfail();

    for(int i=0;i<n;i++) {
        if(op[i] == 'I') {
            mi = 1e9;
            t2.query(s[i], i, n, 0); // 找范围
            t1.query(s[i], i, mi - 1, 1); // 直接统计
        }
    }

    for(int i=0;i<n;i++) {
        if(op[i] == 'Q') {
            cout << ans[i] << '\n';
        }
    }
}
int main() {
    ios::sync_with_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    int t = 1;
   // cin >> t;
    while(t -- ) {
        slove();
    }

    return 0;
}



th_stdio(0),cin.tie(0);
    cout << fixed << setprecision(12);

    int t = 1;
   // cin >> t;
    while(t -- ) {
        slove();
    }

    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值