Description

Input

第一行包含两个整数 N,M,表示城市个数及特征项链的长度。 接下来的N-1 行, 每行两个整数 x,y, 表示城市 x 与城市 y 有直接道路相连。城市由1~N进行编号。接下来的一行,包含一个长度为 N,仅包含小写字母的字符串,第 i 位的字符表示在城市 i 流行的原料类型。 最后一行, 包含一个长度为 M, 仅包含小写字母的字符串, 表示特征字符串。

Output

仅包含一个整数,为 N2 * Expectation

Sample Input

  1. 3 5
  2. 1 2
  3. 1 3
  4. aab
  5. abaab

Sample Output

  1. 15

Solution

点分治+后缀自动机....

思路是真的神奇...看了好久题解才看明白。

先考虑暴力,有一个很显然的\(O(n^2)\)的暴力:

  • 枚举每个点作为起点,\(dfs\)另一个点,\(dfs\)的同时在特征字符串\(S\)的\(SAM\)上跑,顺便统计答案就好了。
  • 这个在\(SAM\)上跑实际上就相当于每次在一个已经匹配了的串后面加一个字符,那么直接沿着\(SAM\)的转换边走就好了。

换个角度思考,还有另一种暴力:

  • 枚举一个点\(x\),统计出每条以这个点为\(lca\)的路径的代价。
  • 考虑这个看起来高级一点的暴力怎么做,
  • 对于点\(x\),路径肯定是\(a\to x \to b\)的形式,那么我们把他拆成两段\(a\to x\)和\(x \to b\),注意到如果我们把特征字符串反过来,那么这两种其实就是一样的,所以我们现在考虑\(a \to x\)怎么统计,然后在翻转过的\(S\)上再做一遍就好了。
  • 那么我们就是要统计出对于自动机上的点\(i\),有多少以\(x\)结尾的路径字符串在这个点。
  • 那么问题就相当于当前已经匹配了一个串,要在这个串前面加一个字符,那么沿着\(parent\)树跳就好了。
  • 最后答案就是对于字符串上每个点,这个点开始和这个点结束的方案之积 的和。

注意到第二种暴力可以用点分治优化,那么第二种暴力的复杂度就是\(O(n\log n+nm)\)。

考虑如何把后面一项优化一下,注意到第一种方案是不需要每次都扫一遍自动机的,所以可以在点分治的时候设一个阀值\(B\),若\(size>B\)就用第二种,否则用第一种。

可以发现当\(B=\sqrt{n}\)的时候复杂度最优,此时时间复杂度为\(O((n+m)\sqrt{n})\)。

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define ll long long
  4. void read(int &x) {
  5. x=0;int f=1;char ch=getchar();
  6. for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-f;
  7. for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';x*=f;
  8. }
  9. void print(ll x) {
  10. if(x<0) putchar('-'),x=-x;
  11. if(!x) return ;print(x/10),putchar(x%10+48);
  12. }
  13. void write(ll x) {if(!x) putchar('0');else print(x);putchar('\n');}
  14. const int maxn = 2e5+10;
  15. const int inf = 1e9;
  16. ll ans;
  17. char s[maxn];
  18. int n,m,rt,siz,B,top;
  19. int head[maxn],tot,a[maxn];
  20. int vis[maxn],f[maxn],sz[maxn],tmp[maxn],t[maxn];
  21. struct edge{int to,nxt;}e[maxn<<1];
  22. void ins(int u,int v) {e[++tot]=(edge){v,head[u]},head[u]=tot;}
  23. struct Suffix_Automaton {
  24. int cnt,qs,lstp;
  25. int par[maxn],tr[maxn][26],ml[maxn],pos[maxn],sz[maxn];
  26. int t[maxn],r[maxn],son[maxn][26],str[maxn],tag[maxn],rev[maxn];
  27. void append(int x,int v) {
  28. int p=lstp,np=++cnt;pos[np]=v,sz[np]=1,ml[np]=ml[p]+1,rev[v]=np;lstp=np;
  29. for(;p&&tr[p][x]==0;p=par[p]) tr[p][x]=np;
  30. if(!p) return par[np]=qs,void();
  31. int q=tr[p][x];
  32. if(ml[p]+1<ml[q]) {
  33. int nq=++cnt;ml[nq]=ml[p]+1;
  34. memcpy(tr[nq],tr[q],sizeof tr[nq]);
  35. par[nq]=par[q],par[q]=par[np]=nq;
  36. for(;p&&tr[p][x]==q;p=par[p]) tr[p][x]=nq;
  37. } else par[np]=q;
  38. }
  39. void prepare(char *ss) {
  40. lstp=qs=cnt=1;
  41. for(int i=1;i<=m;i++) append(str[i]=ss[i]-'a',i);
  42. for(int i=1;i<=cnt;i++) t[ml[i]]++;
  43. for(int i=1;i<=m;i++) t[i]+=t[i-1];
  44. for(int i=1;i<=cnt;i++) r[t[ml[i]]--]=i;
  45. for(int i=cnt;i;i--) {
  46. int p=r[i];
  47. sz[par[p]]+=sz[p];
  48. if(!pos[par[p]]) pos[par[p]]=pos[p];
  49. son[par[p]][str[pos[p]-ml[par[p]]]]=p;
  50. }
  51. }
  52. void mark(int x,int fa,int now,int len) {
  53. if(len==ml[now]) now=son[now][a[x]];
  54. else if(str[pos[now]-len]!=a[x]) now=0;
  55. if(!now) return ;len++;tag[now]++;
  56. for(int i=head[x];i;i=e[i].nxt)
  57. if(e[i].to!=fa&&!vis[e[i].to]) mark(e[i].to,x,now,len);
  58. }
  59. void push() {for(int i=1;i<=cnt;i++) tag[r[i]]+=tag[par[r[i]]];}
  60. }sam1,sam2;
  61. void get_rt(int x,int fa) {
  62. sz[x]=1,f[x]=0;
  63. for(int i=head[x];i;i=e[i].nxt)
  64. if(e[i].to!=fa&&!vis[e[i].to])
  65. get_rt(e[i].to,x),sz[x]+=sz[e[i].to],f[x]=max(f[x],sz[e[i].to]);
  66. f[x]=max(f[x],siz-sz[x]);
  67. if(f[x]<f[rt]) rt=x;
  68. }
  69. void get_node(int x,int fa) {
  70. t[++top]=x;
  71. for(int i=head[x];i;i=e[i].nxt)
  72. if(e[i].to!=fa&&!vis[e[i].to]) get_node(e[i].to,x);
  73. }
  74. void dfs(int x,int fa,int now) {
  75. now=sam1.tr[now][a[x]];
  76. if(!now) return ;
  77. ans+=sam1.sz[now];
  78. for(int i=head[x];i;i=e[i].nxt)
  79. if(e[i].to!=fa&&!vis[e[i].to]) dfs(e[i].to,x,now);
  80. }
  81. void work(int x,int fa,int op) {
  82. memset(sam1.tag,0,(sam1.cnt+2)*4);
  83. memset(sam2.tag,0,(sam2.cnt+2)*4);
  84. if(fa) sam1.mark(x,fa,sam1.tr[1][a[fa]],1),sam2.mark(x,fa,sam2.tr[1][a[fa]],1);
  85. else sam1.mark(x,fa,1,0),sam2.mark(x,fa,1,0);
  86. sam1.push(),sam2.push();
  87. for(int i=1;i<=m;i++) ans+=1ll*op*sam1.tag[sam1.rev[i]]*sam2.tag[sam2.rev[m-i+1]];
  88. }
  89. void solve(int x) {
  90. get_rt(x,0);siz=sz[x];
  91. if(siz<=B) {
  92. top=0,get_node(x,0);
  93. for(int i=1;i<=top;i++) dfs(t[i],0,sam1.qs);
  94. for(int i=1;i<=top;i++) vis[t[i]]=0;
  95. return ;
  96. }
  97. for(int i=head[x];i;i=e[i].nxt) tmp[e[i].to]=sz[e[i].to];
  98. work(x,0,1);
  99. for(int i=head[x];i;i=e[i].nxt) if(!vis[e[i].to]) work(e[i].to,x,-1);
  100. vis[x]=1;
  101. for(int i=head[x];i;i=e[i].nxt)
  102. if(!vis[e[i].to]) siz=tmp[e[i].to],rt=0,get_rt(e[i].to,x),solve(rt);
  103. }
  104. int main() {
  105. read(n),read(m);B=sqrt(n);
  106. for(int i=1,x,y;i<n;i++) read(x),read(y),ins(x,y),ins(y,x);
  107. scanf("%s",s+1);
  108. for(int i=1;i<=n;i++) a[i]=s[i]-'a';
  109. scanf("%s",s+1);
  110. sam1.prepare(s);
  111. reverse(s+1,s+m+1);
  112. sam2.prepare(s);
  113. siz=n,f[0]=inf,get_rt(1,0),solve(rt);write(ans);
  114. return 0;
  115. }

[BZOJ1921] [CTSC2010]珠宝商的更多相关文章

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

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

  2. P4218 [CTSC2010]珠宝商

    P4218 [CTSC2010]珠宝商 神题... 可以想到点分治,细节不写了... (学了个新姿势,sam可以在前面加字符 但是一次点分治只能做到\(O(m)\),考虑\(\sqrt n\)点分治, ...

  3. CTSC2010 珠宝商

    珠宝商 题目描述 Louis.PS 是一名精明的珠宝商,他出售的项链构造独特,很大程度上是因为他的制作方法与众不同.每次 Louis.PS 到达某个国家后,他会选择一条路径去遍历该国的城市.在到达一个 ...

  4. 洛谷P4218 [CTSC2010]珠宝商(后缀自动机+点分治)

    传送门 这题思路太清奇了……->题解 //minamoto #include<iostream> #include<cstdio> #include<cstring ...

  5. @bzoj - 1921@ [ctsc2010]珠宝商

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 简述版题意:给定字符串 S 与一棵树 T,树上每个点有一个字符. ...

  6. bzoj AC倒序

    Search GO 说明:输入题号直接进入相应题目,如需搜索含数字的题目,请在关键词前加单引号 Problem ID Title Source AC Submit Y 1000 A+B Problem ...

  7. Sam做题记录

    Sam做题记录 Hihocoder 后缀自动机二·重复旋律5 求一个串中本质不同的子串数 显然,答案是 \(\sum len[i]-len[fa[i]]\) Hihocoder 后缀自动机三·重复旋律 ...

  8. 【BZOJ1921】【CTSC2010】珠宝商(点分治,后缀自动机)

    [BZOJ1921][CTSC2010]珠宝商(点分治,后缀自动机) 题面 洛谷 BZOJ权限题 题解 如果要我们做暴力,显然可以以某个点为根节点,然后把子树\(dfs\)一遍,建出特征串的\(SAM ...

  9. [CTSC2010]性能优化

    [CTSC2010]性能优化 循环卷积快速幂 两个注意点:n+1不是2^k*P+1形式,任意模数又太慢?n=2^k1*3^k2*5^k3*7^k4 多路分治!深刻理解FFT运算本质:分治,推式子得到从 ...

随机推荐

  1. python 装饰器 回顾 及练习

    # 复习 # 讲作业 # 装饰器的进阶 # functools.wraps # 带参数的装饰器 # 多个装饰器装饰同一个函数 # 周末的作业 # 文件操作 # 字符串处理 # 输入输出 # 流程控制 ...

  2. IO复用——epoll系列系统调用

    1.内核事件表 epoll是Linux特有的I/O复用函数.epoll把用户关心的文件描述上的事件放在内核里的一个事件表中,并用一个额外的文件描述符来标识该内核事件表.这个额外文件描述符使用函数epo ...

  3. ADB工具的安装

    1.Windows ADB工具下载地址: https://developer.android.google.cn/studio/releases/platform-tools ADB工具官网教程: h ...

  4. FIFO队列(First In First Out)和优先队列

    queue<类型名> q; q.size() - 返回队列中元素个数 q.empty() - 若队列为空,返回true ,否则返回false q.pop() - 删除队首元素,但不返回其值 ...

  5. HDU暑假多校第八场J-Taotao Picks Apples

    一.题意 给定一个序列,之后给出若干个修改,修改的内容为在原序列的基础上,将某一位元素的值改成给定的值<每次修改相互独立,不保存修改后的结果>.之后询问,在选择第一位元素的情况下,最长递增 ...

  6. java 第六章 面向对象基础

    1.面向对象编程思想 面向过程编程 传统的C语言属于面向过程编程.面向过程解决问题的思路:通常是分析出解决问题所需要的步骤,然后用方法把这些步骤一步一步实现,最后一个一个依次调用方法来解决. 面向过程 ...

  7. jquery validation remote进行唯一性验证时只使用自定义参数,不使用默认参数

    在使用validation进行唯一性验证时,想各个模块写一个统一的方法,相统一参数名称,但是remote方法会默认把对应的参数传过去 如: 会把role.roleName默认作为变量提交过去 所以想自 ...

  8. LeetCode:26. Remove Duplicates from Sorted Array(Easy)

    1. 原题链接 https://leetcode.com/problems/remove-duplicates-from-sorted-array/description/ 2. 题目要求 给定一个已 ...

  9. error C2248: 'QObject::QObject' : cannot access private member declared in class 'QObject'

    1.error C2471: cannot update program database vc90.pdb 解决方案:https://blog.csdn.net/shuixin536/article ...

  10. 「题目代码」P1007~P1012(Java)

    1007 C基础-计负均正 import java.util.*; import java.io.*; public class Main { public static void main(Stri ...