珠宝商
题目链接:luogu P4218
题目大意
给你一棵树,每个点有一个字符。再给你一个字符串 s。
然后问你树上的所有简单的路径在 s 上的出现次数的和。
思路
一个比较神奇的题目。
首先考虑 \(n^2\) 暴力,不难想象使用 SAM,试着给 \(s\) 串建一下 SAM 看看。
然后不难想象你就枚举根,然后跑树在 SAM 上跑链,然后出现次数其实就是 DAG 能走到的部分的 \(sz\) 和。
(因为那些位置都是你的后缀)
然后考虑别的算法,因为是树上,我们试着用点分治。
那我们考虑把这条路径拆成了两个部分,那我们可以考虑枚举中间衔接的是 \(s\) 串的哪个位置。
(不能枚举颜色,因为可能是在不同的位置)
也就是说我们需要统计子树里面有多少个到根的链满足出现在 \(s\) 中而且末尾为 \(p\)(设 \(p\) 为你当前枚举的位置),还有跟到这个点的字符串开头为 \(p\) 的。
不难看出我们只需要求出一种,另一种反转一下串即可。
那一个比较容易想到的想法是记录一个数组 \(num_i\) 表示 SAM 上 \(i\) 这个位置存了多少贡献。
对于每个那些串我们可以在 SAM 上跑(继承地跑),然后给对于的位置 \(num_i\) 加一。
然后再 DP 一下 \(num_i\) 加上祖先的值 \(num_{fa_i}\) 即可。
找答案的时候 \(s\) 中第 \(i\) 个位置在 SAM 的位置是 \(x\),就直接是 \(num_x\) 了。
但是会发现一个问题,它好像不能继承?
因为你是在前面加字符,不能直接用 \(son_i\) 数组。
那考虑自己再弄一个数组 \(Son_i\):
考虑看它那个 endpose 集合,因为是连续的,所以如果当前的长度不是最大长度,就一定要跟前面那个对应上,对应上了就还是这个位置,否则就没有了。
那如果是最大长度,就一定会往后跳,那就是要找到一个 \(fa\) 是它的点,满足前面能补上你要的那个,否则也没有答案。
那我们只需要记录达到最大长度的那个跳到哪即可,前面那个我们直接可以那个时候推。
求这个 \(Son\) 的时候也不难,就直接每个点给它的父亲的 \(Son\) 数组贡献一下。
(所以我们这里需要记录每个点的 endpose 集合的右端点,随便一个即可,这里是 \(R_i\))
然后看一下复杂度:\(O(n\log n+nm)\)(因为每个点你都要枚举 \(m\) 中间的断点位置)
麻了怎么更垃圾了。
但是观察到带上了 \(m\),而 \(n\) 大看起来是没问题的。
于是考虑一下能不能两个算法都用上,分析一下各自适用处:
暴力就是 \(n^2\),\(n\) 小的时候能用。
这个算法是 \(n\log n+nm\),那 \(n\) 大的时候似乎就可以,因为 \(nm\) 里面的 \(n\) 是你作为断点点数。
那我们考虑一下 \(\leqslant \sqrt{n}\) 的用暴力,别的用第二个算法。
首先看暴力杂度,\(T(n)=2T(\dfrac{n}{2})+O(n^2)\),主定理之类的能证明是 \(n\sqrt{n}\)。
感性理解一下也不难,你每次只用 \(1\) 的大小是 \(O(n)\),每次用 \(\sqrt{n}\) 的大小是 \(\dfrac{n}{\sqrt{n}}\sqrt{n}^2=n\sqrt{n}\)。
也不会用更大的因为会用另一个算法继续点分治。
接着看另一个算法,考虑根号,你深度就 \(\log n\) 层,而且到 \(\sqrt{n}\) 大小的层就不走了,所以 \(nm\) 里面的 \(n\) 就是 \(\sqrt{n}\) 级别的(大概)。
所以就是对的。
具体操作建议看看打代码。
小细节:
两个 SAM 的 \(s\) 数组是不一样的。(第二个是翻转的,后面也要用的 \(s\) 数组也是不一样的)
区分好 \(son_i\) 和 \(Son_i\) 数组,暴力里面只会用到 \(son_i\)!
SAM 经典必写错(好吧这个也许只有我
代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const ll N = 5e4 + 100;
struct node {
ll to, nxt;
}e[N << 1];
ll n, m, le[N], KK, a[N], s[N], B;
ll root, max_root, sz[N], pla1[N], pla2[N];
char s1[N], s2[N];
bool in[N];
ll ans;
void add(ll x, ll y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
}
struct SAM {
struct nde {
ll sz, len, R, fa;
ll son[26], Son[26];
}d[N << 1];
ll tot, lst; ll num[N << 1];
ll tong[N], xl[N << 1], s[N];
void Init() {
tot = lst = 1;
}
ll insert(ll x) {
ll p = lst, np = ++tot; lst = np;
d[np].len = d[p].len + 1; d[np].sz = 1;
d[np].R = d[np].len;
for (; p && !d[p].son[x]; p = d[p].fa) d[p].son[x] = np;
if (!p) d[np].fa = 1;
else {
ll q = d[p].son[x];
if (d[q].len == d[p].len + 1) d[np].fa = q;
else {
ll nq = ++tot; d[nq] = d[q];
d[nq].len = d[p].len + 1; d[nq].sz = 0;
d[np].fa = d[q].fa = nq; d[nq].R = 0;
for (; p && d[p].son[x] == q; p = d[p].fa) d[p].son[x] = nq;
}
}
return np;
}
void build() {
for (ll i = 1; i <= m; i++) tong[i] = 0;
for (ll i = 1; i <= tot; i++) tong[d[i].len]++;
for (ll i = 1; i <= m; i++) tong[i] += tong[i - 1];
for (ll i = 1; i <= tot; i++) xl[tong[d[i].len]--] = i;
for (ll i = tot; i > 1; i--) {
ll now = xl[i];
d[d[now].fa].sz += d[now].sz;
d[d[now].fa].R = d[now].R;
d[d[now].fa].Son[s[d[now].R - d[d[now].fa].len]] = now;
}
}
void clear() {
for (ll i = 1; i <= tot; i++) num[i] = 0;
}
void clac(ll now, ll father, ll x, ll len) {
if (d[x].len == len) x = d[x].Son[a[now]];//到了最大长度
else if (s[d[x].R - len] != a[now]) x = 0;//没到最大长度,直接看是否对上字符
if (!x) return ; num[x]++;
for (ll i = le[now]; i; i = e[i].nxt)
if (e[i].to != father && !in[e[i].to])
clac(e[i].to, now, x, len + 1);
}
void DP() {
for (ll i = 2; i <= tot; i++) {
ll now = xl[i];
num[now] += num[d[now].fa];
}
}
}S1, S2;
void dfs(ll now, ll father) {
sz[now] = 1;
for (ll i = le[now]; i; i = e[i].nxt)
if (e[i].to != father && !in[e[i].to]) {
dfs(e[i].to, now); sz[now] += sz[e[i].to];
}
}
void get_root(ll now, ll father, ll sum) {
ll maxn = sum - sz[now];
for (ll i = le[now]; i; i = e[i].nxt)
if (e[i].to != father && !in[e[i].to]) {
get_root(e[i].to, now, sum);
maxn = max(maxn, sz[e[i].to]);
}
if (maxn < max_root) max_root = maxn, root = now;
}
//
ll st[N];
void dfs1(ll now, ll father) {
st[++st[0]] = now;
for (ll i = le[now]; i; i = e[i].nxt)
if (e[i].to != father && !in[e[i].to])
dfs1(e[i].to, now);
}
void dfs2(ll now, ll father, ll pl) {
pl = S1.d[pl].son[a[now]]; if (!pl) return ;
ans += S1.d[pl].sz;
for (ll i = le[now]; i; i = e[i].nxt)
if (e[i].to != father && !in[e[i].to]) {
dfs2(e[i].to, now, pl);
}
}
void clacnn(ll now) {
st[0] = 0; dfs1(now, 0);
for (ll i = 1; i <= st[0]; i++) {
dfs2(st[i], 0, 1);
}
}
//n^2
void clac(ll now, ll father, ll op) {
S1.clear(); S2.clear();
if (father) {
S1.clac(now, 0, S1.d[1].Son[a[father]], 1);
S2.clac(now, 0, S2.d[1].Son[a[father]], 1);
}
else {
S1.clac(now, 0, 1, 0); S2.clac(now, 0, 1, 0);
}
S1.DP(); S2.DP();
for (ll i = 1; i <= m; i++)
ans += op * S1.num[pla1[i]] * S2.num[pla2[m - i + 1]];//记得第二个串翻转了所以第二个是 m-i+1
}
void slove(ll now) {
in[now] = 1;
dfs(now, 0);
if (sz[now] <= B) {//n^2
in[now] = 0;
clacnn(now);
in[now] = 1;
return ;
}
clac(now, 0, 1);
for (ll i = le[now]; i; i = e[i].nxt)
if (!in[e[i].to]) {
clac(e[i].to, now, -1);
max_root = sz[e[i].to] + 1;
get_root(e[i].to, now, sz[e[i].to]);
slove(root);
}
}
int main() {
scanf("%lld %lld", &n, &m); B = sqrt(n);
// B = 1;
for (ll i = 1; i < n; i++) {
ll x, y; scanf("%lld %lld", &x, &y);
add(x, y); add(y, x);
}
scanf("%s", s1 + 1); scanf("%s", s2 + 1);
for (ll i = 1; i <= n; i++) a[i] = s1[i] - 'a';
for (ll i = 1; i <= m; i++) s[i] = s2[i] - 'a';
S1.Init();
for (ll i = 1; i <= m; i++) pla1[i] = S1.insert(s[i]), S1.s[i] = s[i];
S1.build();
reverse(s + 1, s + m + 1);
S2.Init();
for (ll i = 1; i <= m; i++) pla2[i] = S2.insert(s[i]), S2.s[i] = s[i];
S2.build();
reverse(s + 1, s + m + 1);
dfs(1, 0);
max_root = n + 1;
get_root(1, 0, n);
slove(root);
printf("%lld", ans);
return 0;
}
标签:include,SAM,ll,分治,sqrt,fa,now,根号
From: https://www.cnblogs.com/Sakura-TJH/p/luogu_P4218.html