珠宝商
题目大意
给你一棵树,每个点有一个字符。再给你一个字符串 s。
然后问你树上的所有简单的路径在 s 上的出现次数的和。
思路
一个比较神奇的题目。
首先考虑 n 2 n 2 暴力,不难想象使用 SAM,试着给 s s 串建一下 SAM 看看。
然后不难想象你就枚举根,然后跑树在 SAM 上跑链,然后出现次数其实就是 DAG 能走到的部分的 s z s z 和。
(因为那些位置都是你的后缀)
然后考虑别的算法,因为是树上,我们试着用点分治。
那我们考虑把这条路径拆成了两个部分,那我们可以考虑枚举中间衔接的是 s s 串的哪个位置。
(不能枚举颜色,因为可能是在不同的位置)
也就是说我们需要统计子树里面有多少个到根的链满足出现在 s s 中而且末尾为 p p (设 p p 为你当前枚举的位置),还有跟到这个点的字符串开头为 p p 的。
不难看出我们只需要求出一种,另一种反转一下串即可。
那一个比较容易想到的想法是记录一个数组 n u m i n u m i 表示 SAM 上 i i 这个位置存了多少贡献。
对于每个那些串我们可以在 SAM 上跑(继承地跑),然后给对于的位置 n u m i n u m i 加一。
然后再 DP 一下 n u m i n u m i 加上祖先的值 n u m f a i n u m f a i 即可。
找答案的时候 s s 中第 i i 个位置在 SAM 的位置是 x x ,就直接是 n u m x n u m x 了。
但是会发现一个问题,它好像不能继承?
因为你是在前面加字符,不能直接用 s o n i s o n i 数组。
那考虑自己再弄一个数组 S o n i S o n i :
考虑看它那个 endpose 集合,因为是连续的,所以如果当前的长度不是最大长度,就一定要跟前面那个对应上,对应上了就还是这个位置,否则就没有了。
那如果是最大长度,就一定会往后跳,那就是要找到一个 f a f a 是它的点,满足前面能补上你要的那个,否则也没有答案。
那我们只需要记录达到最大长度的那个跳到哪即可,前面那个我们直接可以那个时候推。
求这个 S o n S o n 的时候也不难,就直接每个点给它的父亲的 S o n S o n 数组贡献一下。
(所以我们这里需要记录每个点的 endpose 集合的右端点,随便一个即可,这里是 R i R i )
然后看一下复杂度:O ( n log n + n m ) O ( n log n + n m ) (因为每个点你都要枚举 m m 中间的断点位置)
麻了怎么更垃圾了。
但是观察到带上了 m m ,而 n n 大看起来是没问题的。
于是考虑一下能不能两个算法都用上,分析一下各自适用处:
暴力就是 n 2 n 2 ,n n 小的时候能用。
这个算法是 n log n + n m n log n + n m ,那 n n 大的时候似乎就可以,因为 n m n m 里面的 n n 是你作为断点点数。
那我们考虑一下 ⩽ √ n ⩽ n 的用暴力,别的用第二个算法。
首先看暴力杂度,T ( n ) = 2 T ( n 2 ) + O ( n 2 ) T ( n ) = 2 T ( n 2 ) + O ( n 2 ) ,主定理之类的能证明是 n √ n n n 。
感性理解一下也不难,你每次只用 1 1 的大小是 O ( n ) O ( n ) ,每次用 √ n n 的大小是 n √ n √ n 2 = n √ n n n n 2 = n n 。
也不会用更大的因为会用另一个算法继续点分治。
接着看另一个算法,考虑根号,你深度就 log n log n 层,而且到 √ n n 大小的层就不走了,所以 n m n m 里面的 n n 就是 √ n n 级别的(大概)。
所以就是对的。
具体操作建议看看打代码。
小细节:
两个 SAM 的 s s 数组是不一样的。(第二个是翻转的,后面也要用的 s s 数组也是不一样的)
区分好 s o n i s o n i 和 S o n i S o n i 数组,暴力里面只会用到 s o n i s o n 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 );
}
}
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 ]];
}
void slove (ll now) {
in[now] = 1 ;
dfs (now, 0 );
if (sz[now] <= B) {
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);
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 ;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现