题目大意:
有一个字符串的集合,有以下三种操作:
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;
}