Codeforces 547E Mike and Friends
蔡队题:AC自动机上fail树dfs序建可持久化线段树
https://codeforces.com/problemset/problem/547/E
题意:给 \(n\) 个串,串总长不超过 \(2e5\),\(q\) 次询问,每次询问 \(l\to r\) 这些串中出现了多少次 \(k\) 串。
题解:
考虑x个串,求k的出现次数,可以对这x个串建AC自动机,每个点记录被这x个串一共访问了多少次,然后把k串放在上面跑所有的逆fail指针,每到一个点就把它的权值加上即是答案。
那么多次询问下,将所有串建立AC自动机,如果我想询问 \(l\to r\) 这些串出现了多少次 \(k\) 串,只需要计算 \(l \to r\) 这些串对答案的总贡献, \(k\) 串就直接在fail树上表达为一个子树。
而在insert一个串时记录它末尾字符在字典树的序号,然后这个点在fail树上的子树和可以直接dfs序维护,问题变成了在一棵树上如何计算子树和、单点加。
还有一个问题没有处理,每次询问的 \(l \to r\) 是不同的,由于答案互不影响,考虑差分 \(ans(r)-ans(l-1)\),那么将询问排序,再扫描线求值即可。
当然这里直接写带历史版本的权值线段树可以做到在线询问,复杂度会多一个log。
代码(树状数组离线询问版本):
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/6/20 1:41
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5 + 50;
const int mod = 1e9 + 7;
ll qp(ll a, ll n, ll mod = ::mod) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
int now[maxn], out[maxn];
int tot;
class AC_automation {
public:
int trie[maxn][26], cnt;
int fail[maxn], in[maxn], Map[maxn], fa[maxn];
void insert(char *str, int id) {
int root = 0;
for (int i = 0; str[i]; i++) {
int id = str[i] - 'a';
if (!trie[root][id]) trie[root][id] = ++cnt;
fa[trie[root][id]] = root;
root = trie[root][id];
}
Map[id] = root;
}
vector<int> edge[maxn];
void build() {
queue<int> que;
for (int i = 0; i < 26; i++) if (trie[0][i]) {
edge[0].push_back(trie[0][i]);
// cerr << "add edge: " << 0 << " " << trie[0][i] << endl;
que.push(trie[0][i]);
}
while (!que.empty()) {
int k = que.front();
que.pop();
for (int i = 0; i < 26; i++) {
if (trie[k][i]) {
fail[trie[k][i]] = trie[fail[k]][i];
edge[fail[trie[k][i]]].push_back(trie[k][i]);
// cerr << "add edge: " << fail[trie[k][i]] << " " << trie[k][i] << endl;
que.push(trie[k][i]);
in[fail[trie[k][i]]]++;
} else trie[k][i] = trie[fail[k]][i];
}
}
}
void dfs(int u) {
now[u] = ++tot;
for (auto v : edge[u]) {
dfs(v);
}
out[u] = tot;
}
} AC;
struct que {
int x, k, sgn, pos;
que(int i, int i1, int i2, int i3):x(i),k(i1),sgn(i2),pos(i3) {}
bool operator < (const que& oth) const {
return x < oth.x;
}
};
int res[500005];
int N;
int pre[maxn];
void add(int pos) {
while (pos <= N) {
pre[pos] ++;
pos += pos & -pos;
}
}
int query(int pos) {
int res = 0;
while (pos > 0) {
res += pre[pos];
pos -= pos & -pos;
}
return res;
}
char s[maxn];
int main(int argc, char *argv[]) {
int n, q;
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i) {
scanf("%s", s);
AC.insert(s, i);
}
AC.build();
AC.dfs(0);
N = out[0];
vector<que> ned;
for (int i = 1; i <= q; ++i) {
int x, y, k;
scanf("%d%d%d", &x, &y, &k);
ned.emplace_back(x - 1, k, -1, i);
ned.emplace_back(y, k, 1, i);
}
sort(ned.begin(), ned.end());
int pos = 1;
for (auto i : ned) {
while (pos <= i.x) {
// cerr << "***add: " << pos << endl;
int nowx = AC.Map[pos];
while (nowx) {
// cerr << now[nowx] << " ";
add(now[nowx]), nowx = AC.fa[nowx];
}
// cerr << endl;
pos ++;
}
res[i.pos] += i.sgn * (query(out[AC.Map[i.k]]) - query(now[AC.Map[i.k]] - 1));
}
for (int i = 1; i <= q; ++i) {
printf("%d\n", res[i]);
}
return 0;
}
主席树版本:
/*================================================================
* 文件名称:E.cpp
* 创 建 者: badcw
* 创建日期: 10/22/19
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5+50;
const int mod = 1e9+7;
ll qp(ll a, ll n) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
template <class T>
inline bool scan(T& ret) {
char c;
int sgn;
if (c = getchar(), c == EOF) return 0; // EOF
while (c != '-' && (c < '0' || c > '9')) c = getchar();
sgn = (c == '-') ? -1 : 1;
ret = (c == '-') ? 0 : (c - '0');
while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0');
ret *= sgn;
return 1;
}
//template <class T>
//inline void out(T x) {
// if (x > 9) out(x / 10);
// putchar(x % 10 + '0');
//}
int root[maxn];
int cnt = 0;
struct node {
int l, r, val;
}p[maxn * 40];
void update(int l, int r, int pre, int &now, int pos) {
if (pre == now) {
p[now=++cnt] = p[pre];
}
p[now].val ++;
if (l == r) return;
int mid = l + r >> 1;
if (pos <= mid) update(l, mid, p[pre].l, p[now].l, pos);
else update(mid + 1, r, p[pre].r, p[now].r, pos);
}
int le, re;
inline int query(int l, int r, int x, int y) {
if (p[y].val == p[x].val) return 0;
if (le <= l && r <= re) {
return p[y].val - p[x].val;
}
int mid = l + r >> 1;
int res = 0;
if (le <= mid) res += query(l, mid, p[x].l, p[y].l);
if (re > mid) res += query(mid + 1, r, p[x].r, p[y].r);
return res;
}
int n;
string s[maxn];
vector<int> edge[maxn];
int pos[maxn];
int rnk[maxn], ot[maxn];
int tot;
class AC_automation
{
public:
int trie[maxn][26];
int tag[maxn];
int fail[maxn], num[maxn], res[maxn], in[maxn];
void init() {
memset(trie, 0, sizeof trie);
memset(tag, 0, sizeof tag);
memset(fail, 0, sizeof fail);
}
int insert(string str) {
int root = 0;
for (int i = 0; i < str.length(); i++)
{
int id = str[i] - 'a';
if (!trie[root][id]) {
trie[root][id] = ++n;
}
root = trie[root][id];
}
return root;
}
void build() {
queue<int> que;
for (int i = 0; i < 26; i++) if (trie[0][i]) que.push(trie[0][i]);
while (!que.empty())
{
int k = que.front();
que.pop();
for (int i = 0; i < 26; i++)
{
if (trie[k][i])
{
fail[trie[k][i]] = trie[fail[k]][i];
que.push(trie[k][i]);
in[fail[trie[k][i]]] ++;
} else trie[k][i] = trie[fail[k]][i];
}
}
}
} AC;
inline void build(int pre, int &now, int id) {
int rt = 0;
now = pre;
for (char i : s[id]) {
rt = AC.trie[rt][i - 'a'];
update(1, n, pre, now, rnk[rt]);
}
}
inline void dfs(int u) {
rnk[u] = ++tot;
for (auto v : edge[u]) {
dfs(v);
}
ot[u] = tot;
}
int N, Q;
int main(int argc, char* argv[]) {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> N >> Q;
for (int i = 1; i <= N; ++i) {
cin >> s[i];
pos[i] = AC.insert(s[i]);
}
n ++;
AC.build();
for (int i = 1; i <= n; ++i) {
edge[AC.fail[i]].push_back(i);
}
dfs(0);
for (int i = 1; i <= N; ++i) build(root[i - 1], root[i], i);
while (Q--) {
int l, r, k;
cin >> l >> r >> k;
le = rnk[pos[k]], re = ot[pos[k]];
cout << query(1, n, root[l - 1], root[r]) << endl;
}
return 0;
}