珠宝商

题目链接:luogu P4218

题目大意

给你一棵树,每个点有一个字符。再给你一个字符串 s。
然后问你树上的所有简单的路径在 s 上的出现次数的和。

思路

一个比较神奇的题目。

首先考虑 n2n^2n2 暴力,不难想象使用 SAM,试着给 sss 串建一下 SAM 看看。
然后不难想象你就枚举根,然后跑树在 SAM 上跑链,然后出现次数其实就是 DAG 能走到的部分的 szszsz 和。
(因为那些位置都是你的后缀)

然后考虑别的算法,因为是树上,我们试着用点分治。
那我们考虑把这条路径拆成了两个部分,那我们可以考虑枚举中间衔接的是 sss 串的哪个位置。
(不能枚举颜色,因为可能是在不同的位置)

也就是说我们需要统计子树里面有多少个到根的链满足出现在 sss 中而且末尾为 ppp(设 ppp 为你当前枚举的位置),还有跟到这个点的字符串开头为 ppp 的。
不难看出我们只需要求出一种,另一种反转一下串即可。

那一个比较容易想到的想法是记录一个数组 numinum_inumi​ 表示 SAM 上 iii 这个位置存了多少贡献。
对于每个那些串我们可以在 SAM 上跑(继承地跑),然后给对于的位置 numinum_inumi​ 加一。
然后再 DP 一下 numinum_inumi​ 加上祖先的值 numfainum_{fa_i}numfai​​ 即可。
找答案的时候 sss 中第 iii 个位置在 SAM 的位置是 xxx,就直接是 numxnum_xnumx​ 了。

但是会发现一个问题,它好像不能继承?
因为你是在前面加字符,不能直接用 sonison_isoni​ 数组。
那考虑自己再弄一个数组 SoniSon_iSoni​:
考虑看它那个 endpose 集合,因为是连续的,所以如果当前的长度不是最大长度,就一定要跟前面那个对应上,对应上了就还是这个位置,否则就没有了。
那如果是最大长度,就一定会往后跳,那就是要找到一个 fafafa 是它的点,满足前面能补上你要的那个,否则也没有答案。

那我们只需要记录达到最大长度的那个跳到哪即可,前面那个我们直接可以那个时候推。
求这个 SonSonSon 的时候也不难,就直接每个点给它的父亲的 SonSonSon 数组贡献一下。
(所以我们这里需要记录每个点的 endpose 集合的右端点,随便一个即可,这里是 RiR_iRi​)

然后看一下复杂度:O(nlog⁡n+nm)O(n\log n+nm)O(nlogn+nm)(因为每个点你都要枚举 mmm 中间的断点位置)
麻了怎么更垃圾了。

但是观察到带上了 mmm,而 nnn 大看起来是没问题的。
于是考虑一下能不能两个算法都用上,分析一下各自适用处:
暴力就是 n2n^2n2,nnn 小的时候能用。
这个算法是 nlog⁡n+nmn\log n+nmnlogn+nm,那 nnn 大的时候似乎就可以,因为 nmnmnm 里面的 nnn 是你作为断点点数。
那我们考虑一下 ⩽n\leqslant \sqrt{n}⩽n​ 的用暴力,别的用第二个算法。
首先看暴力杂度,T(n)=2T(n2)+O(n2)T(n)=2T(\dfrac{n}{2})+O(n^2)T(n)=2T(2n​)+O(n2),主定理之类的能证明是 nnn\sqrt{n}nn​。
感性理解一下也不难,你每次只用 111 的大小是 O(n)O(n)O(n),每次用 n\sqrt{n}n​ 的大小是 nnn2=nn\dfrac{n}{\sqrt{n}}\sqrt{n}^2=n\sqrt{n}n​n​n​2=nn​。
也不会用更大的因为会用另一个算法继续点分治。

接着看另一个算法,考虑根号,你深度就 log⁡n\log nlogn 层,而且到 n\sqrt{n}n​ 大小的层就不走了,所以 nmnmnm 里面的 nnn 就是 n\sqrt{n}n​ 级别的(大概)。
所以就是对的。

具体操作建议看看打代码。
小细节:
两个 SAM 的 sss 数组是不一样的。(第二个是翻转的,后面也要用的 sss 数组也是不一样的)
区分好 sonison_isoni​ 和 SoniSon_iSoni​ 数组,暴力里面只会用到 sonison_isoni​!
SAM 经典必写错(好吧这个也许只有我

代码

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long longusing 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^2void 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^2in[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;
}

【luogu P4218】珠宝商(SAM)(点分治)(根号分治)相关推荐

  1. [Ctsc2010]珠宝商 SAM+点分治+根号分治

    Description 给定一个n个点的树,树上每个点有一个字符,再给一个长度为m的串. 两点的价值为:两点连接形成的字符串再m串中出现的次数. 询问两两点价值的和. Sample Input 3 5 ...

  2. [CTSC2010]珠宝商(点分治+根号分治+后缀自动机)

    [CTSC2010]珠宝商 洛谷题目传送门 简要题意 给定一颗nnn个节点的树,和一个长度为mmm的模式串SSS 树上每个节点都有一个字符 求树上所有路径的点的字符拼成的字符串在SSS中的出现次数之和 ...

  3. [CTSC2010]珠宝商 SAM+后缀树+点分治

    [CTSC2010]珠宝商 不错的题目 看似无法做,n<=5e4,8s,根号算法? 暴力一: n^2,+SAM上找匹配点的right集合sz,失配了直接退出 暴力二: O(m) 统计过lca=x ...

  4. bzoj1921 [CTSC2010]珠宝商 SAM+后缀树+点分治

    Description 有一棵n个节点的树和一个长度为m的字符串S,树上每个节点有一个字符.问对于任意的有序数对(x,y),从x到y路径组成的字符串在S中出现次数的和. n,m≤5⋅104n,m\le ...

  5. [HDU 6643] Ridiculous Netizens(点分治+根号分治+dp)

    HDU 6643 Ridiculous Netizens problem hdu6643 题目大意:给定一棵无根树,以及每个点的点权 wiw_iwi​. 定义一个连通块的价值为连通块内点的点权之积. ...

  6. 学习计划——根号分治

    根号分治 根号分治是一种思想,一般根据一个数(可以是数组的数,也可以是答案的数)分类,分为大于 s q r t ( n ) sqrt(n) sqrt(n)的部分和小于等于 s q r t ( n ) ...

  7. 浅谈根号分治——暴力的美学

    根号分治 根号分治的概念 [模板]P3396 哈希冲突 CF103D Time to Raid Cowavans 根号分治的概念 根号分治是一种优化暴力算法. 我个人的理解就是这东西跟分块差不多.但应 ...

  8. 【CTSC2010】珠宝商【后缀自动机】【点分治】【根号分治】

    题意:给一棵 nnn 个点的树,每个点有个字符,另给一个长度为 mmm 的特征串,求树上 n2n^2n2 条有向路径在特征串中出现的次数之和. n,m≤5×104n,m\leq 5\times 10^ ...

  9. CF1039E-Summer Oenothera Exhibition【LCT,根号分治】

    正题 题目链接:https://www.luogu.com.cn/problem/CF1039E 题目大意 给出nnn个数的序列,mmm次询问至少将这个序列分成多少段才能满足每一段的和不超过w−qiw ...

最新文章

  1. 串口接收数据与分析处理
  2. Golang学习 - bufio 包
  3. Python dataframe修改列顺序(pandas学习)
  4. UA MATH523A 实分析3 积分理论例题 Fubini定理计算一元积分
  5. 博客园电子月刊[第一期]
  6. 【2012百度之星/初赛上】D:轮子上的度度熊
  7. TiDB DM部署及使用
  8. 微服务开发过程中需要注意的若干事项
  9. JavaScript中数组去重汇总
  10. 【Shiro第二篇】SpringBoot + Shiro实现用户身份认证功能
  11. codeforces-760-B Frodo and pillows(二分+数学)
  12. python数据可视化案例 淘宝粽子_Python可视化对比分析淘宝低价人群和匿名用户的淘宝连衣裙数据...
  13. C++设计模式-中介者模式详解
  14. 网络安全学习笔记——蓝队实战攻防
  15. hutool工具类生成二维码案例
  16. 有利润表模板的BI软件有哪些?
  17. 第五章——大数定律和中心极限定理
  18. aspose将word转换为pdf[aspose.word.java 18.11]
  19. mysql怎么创建和调用out参数的存储过程
  20. 新华三+福建工院,给数字人才培养“上一课”!

热门文章

  1. 如何解决请将磁盘插入U盘(F)的情况
  2. Kotlin中对象检查判断
  3. mysql week weekofyear_MySQL WEEKOFYEAR()用法及代码示例
  4. 黑电行业越来越难,海信、TCL、创维、康佳、长虹急需破局
  5. matlab实现lzw编码,LZW编码算法matlab实现
  6. selenium Gird
  7. ArrayList和LinkedList的异同
  8. html表格只设外边框,只设内边框
  9. Windows server 2016 安装sql server
  10. 每日codingame小游戏练习[2021.3.29](python3入门学习之rstrip方法)