题解 P2336 [SCOI2012] 喵星球上的点名
- P2336 [SCOI2012]喵星球上的点名
题目描述
有
还有
有两问 :
-
对于每次点名询问有多少只喵喊“到”。
-
对于每一只喵问询她喊了多少次“到”。
字符集
正解
简单分析
先可以把一只喵的名和姓合并在一起,中间插入一个不存在的字符,这样就不需要考虑两个串了。
询问是类似于字符串
如果字符串出现多次算多次的话,这里有一道用 AC 自动机 经典的例题。
考虑 AC 自动机
如果字符串
也就是说,
判断
但是暴力跳 但是好像也可以通过此题,这里给出一个复杂度为
第一问
对于一个名字串的每一个前缀(总前缀个数不超过字符串总长),覆盖它到根的路径(覆盖表示加多次算一次)。
对每一个名字串都这么做,看点名串总共被多少个名字串给覆盖。
树上链修改,单点查询的问题先转化成树上单点修改,子树查询的问题j。
由于覆盖多次算只算一次,就要把覆盖多的部分减掉。
这里有一个小 trick。
对名字串的前缀按
这样就可以做覆盖多次算一次了。
第二问
对于一个名字串的所有一个前缀,看它们总共覆盖了多少点名串。
树上单点修改,链查询的问题先转化串树上子树修改, 单点查询的问题。
同样利用上面的 trick,减掉
复杂度分析
询问都只需要用到排序和树状数组,这一部分复杂度为
但是有一部分的复杂度很迷,就是
求哪位大佬帮忙证明一下复杂度,或者直接 hack 掉这种做法。
update :
字符集比较大的时候确实这样写确实不对的,无论是普通写法(将不存在的
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 5e4 + 5;
const int M = 1e5 + 5;
const int S = (N + M) << 1;
int n, m;
int namePos[N], queryPos[M];
int last, vcnt = 0;
struct node {
int fa, fail; // 此处的 fa 为 trie 树上的 fa
map<int, int> to;
} a[S];
int que[S];
int siz[S], dep[S], son[S], fa[S]; // 此处的 fa 相当于 ac 自动机上的 fail
int dfn[S], top[S], dfc;
vector<int> g[S];
namespace BIT {
#define lowbit(x) (x & -x)
int c[S];
void clear() { memset(c, 0, sizeof c); }
void update(int p, int v) {
for(int i = p; i <= dfc; i += lowbit(i))
c[i] += v;
}
int sum(int p) {
int res = 0;
for(int i = p; i; i -= lowbit(i))
res += c[i];
return res;
}
int query(int l, int r) { return sum(r) - sum(l - 1); }
#undef lowbit
} // namespace BIT
inline int read() {
int x = 0; char ch = getchar();
while(!isdigit(ch)) ch = getchar();
while(isdigit(ch)) x = x * 10 + ch - '0', ch = getchar();
return x;
}
void extend(int c) {
int &v = a[last].to[c];
if(!v) v = ++vcnt, a[v].fa = last;
last = v;
}
int getFail(int u, int c) {
if(a[u].to.count(c)) return a[u].to[c];
else if(!u) return u;
return a[u].to[c] = getFail(a[u].fail, c);
}
void buildFailTree() {
int hd = 1, tl = 0;
for(auto pr : a[0].to)
que[++tl] = pr.second;
while(hd <= tl) {
int u = que[hd++];
for(auto pr : a[u].to) {
a[pr.second].fail = getFail(a[u].fail, pr.first);
que[++tl] = pr.second;
}
}
for(int i = 1; i <= vcnt; ++i)
g[a[i].fail].push_back(i);
}
void preDfs(int u) {
siz[u] = 1;
dfn[u] = ++dfc;
dep[u] = dep[fa[u]] + 1;
for(int v : g[u]) if(v != fa[u]) {
fa[v] = u, preDfs(v);
siz[u] += siz[v];
if(!son[u] || siz[v] > siz[son[u]])
son[u] = v;
}
}
void getTop(int u, int tp) {
top[u] = tp;
if(son[u]) getTop(son[u], tp);
for(int v : g[u]) if(v != fa[u] && v != son[u]) {
getTop(v, v);
}
}
inline int lca(int u, int v) {
while(top[u] != top[v]) {
if(dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
return dep[u] < dep[v] ? u : v;
}
int arr[N], tot;
bool cmp(const int &u, const int &v) { return dfn[u] < dfn[v]; }
void solve1() {
BIT::clear();
for(int i = 1, u; i <= n; ++i) {
u = namePos[i], tot = 0;
while(u) {
arr[++tot] = u;
BIT::update(dfn[u], 1);
u = a[u].fa;
}
sort(arr + 1, arr + tot + 1, cmp);
for(int j = 1; j < tot; ++j)
BIT::update(dfn[lca(arr[j], arr[j + 1])], -1);
}
for(int i = 1, u; i <= m; ++i) {
u = queryPos[i];
printf("%d\n", BIT::query(dfn[u], dfn[u] + siz[u] - 1));
}
}
void solve2() {
BIT::clear();
for(int i = 1, u; i <= m; ++i) {
u = queryPos[i];
BIT::update(dfn[u], 1);
BIT::update(dfn[u] + siz[u], -1);
}
for(int i = 1, u, res; i <= n; ++i) {
u = namePos[i], tot = 0, res = 0;
while(u) {
arr[++tot] = u;
res += BIT::sum(dfn[u]);
u = a[u].fa;
}
sort(arr + 1, arr + tot + 1, cmp);
for(int j = 1; j < tot; ++j)
res -= BIT::sum(dfn[lca(arr[j], arr[j + 1])]);
printf("%d%c", res, " \n"[i == n]);
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("P2336.in", "r", stdin);
freopen("P2336.out", "w", stdout);
#endif
n = read(), m = read();
for(int i = 1, l, c; i <= n; ++i) {
last = 0;
l = read();
for(int j = 1; j <= l; ++j) {
c = read();
extend(c);
} extend(-1);
l = read();
for(int j = 1; j <= l; ++j) {
c = read();
extend(c);
}
namePos[i] = last;
}
for(int i = 1, l, c; i <= m; ++i) {
last = 0;
l = read();
for(int j = 1; j <= l; ++j) {
c = read();
extend(c);
}
queryPos[i] = last;
}
buildFailTree();
preDfs(0);
getTop(0, 0);
solve1();
solve2();
return 0;
}