珠宝商

题目描述

Louis.PS 是一名精明的珠宝商,他出售的项链构造独特,很大程度上是因为他的制作方法与众不同。每次 Louis.PS 到达某个国家后,他会选择一条路径去遍历该国的城市。在到达一个城市后,他会使用在这个城市流行的材料制作一颗珠子,并按照城市被访问的顺序将珠子串联做成项链,为了使制作出来的项链不会因为城市之间的竞争而影响销量,路径中同一个城市不会重复出现(因为如果项链中 $A$ 城市的材料比 $B$ 城市的材料使用的多,则项链在 $B$ 城市的宣传可能会受到影响)。经过多年对消费者的调查, Louis.PS 已经掌握了判断一条项链吸引消费者程度的方法,具体来说, Louis.PS 经过调查得出了受消费者欢迎的项链的特征,并基于此制作了一个长项链(Louis.PS 称之为特征项链)。对于一条待售的项链,这条项链在特征项链里出现的次数越多,这条项链就越受消费者欢迎。

考虑到现实情况的复杂性,我们对条件做出适当的简化。 对于每个国家,在某些城市间存在道路直接相连,对于两个不同的城市,有且仅有一条路径连接这两个城市(即国家是连通的,且不存在一个环)。对于每个城市,我们用一个小写字母来表示在这个城市流行的材料类型。这样,我们就可以用一个仅包含小写字母的字符串来表示一条项链, 我们将特征项链所对应的字符串称作特征字符串,设为 $EigenString[1...M]$,$M$ 为特征项链的长度。对于一条项链,假设其对应字符串为 $P[1...L]$,$L$ 为这条项链的长度。如果存在一个正整数$K$, 使$EigenString[K...K+L-1]=P[1..L]$,称这条项链在特征项链中出现了一次。 满足上述 条件的正整数$K$的个数即为这条项链在特征项链的出现次数,记为$Popularity(P)$。

Louis.PS 使用数学中的期望概念来评价一个国家是否适合珠宝的采集,对于一个包含 $N$ 个城市的国家,令 $Str_{u,v}$ 表示沿着从 $u$ 开始,至 $v$ 结束的路径所得到的项链的对应字符串。 ($Str_{u,v}$ 与 $Str_{v,u}$ 表示的串一般不相同),则
$$Expectation=\sum_{u,v} Popularity(Str_{u,v}) / N^2$$
对于如下的例子(图中实线表示两端点的国家有直接道路相连):

$N=3$,所流行的材料类型分别为 $\tt{a,a,b}$。

$$Expectation=\dfrac{3+1+2+1+3+1+1+1+2}{9}=\dfrac{5}{3}$$
对于一个国家, Louis.PS 想知道其 Expectation 的值,但苦于计算期望的工作量太大。作为珠宝店的学徒, 你当然不愿放过难得在老板面前展示自己的机会。

输入输出格式

输入格式:

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

输出格式:

输出文件 $\tt{jewelry.out}$ 仅包含一个整数,为 $N^2 * Expectation$。

输入输出样例

输入样例#1:
复制

  1. 3 5
  2. 1 2
  3. 1 3
  4. aab
  5. abaab
输出样例#1:
复制

  1. 15

说明

有 $20\%$的数据,满足$M \leq 1000$;

有 $40\%$的数据,满足ܰ$N \leq 8000, M \leq 50000$;

对于 $100\%$的数据,$N,M \leq 50000$。

题解

参照张天扬《后缀自动机及其应用》和SFN1036的题解。

因为是求出现次数,显然可以用到后缀自动机来做。 首先考虑两种不同的暴力做法:

暴力1

枚举每个点作为起点,然后把整棵树dfs一次,求出起点到每个点组成的路径的出现次数。由于sam的转移是\(O(1)\)的,所以这么做总的复杂度是\(O(n^2)\)。

暴力2

我们考虑求每个点作为路径的lca时候的贡献。设路径的lca为点Z,那么对于一条路径(X,Y),我们可以将其拆成(X,Z)和(Z,Y)两条路径。

考虑Z上的字符在S中的出现位置。那么(X,Z)这一段在S中出现位置的right一定是Z出现位置的right的子集。同理,把(Z,Y)这一段反过来,那么它在S的反串中出现位置的子集的right也必然是Z在反串中出现位置的子集。那么从Z开始dfs,每次维护当前串在原串和反串的后缀树上的位置。然后我们将两个后缀树从上向下递推一遍就可以求出每一个节点的匹配数量了。最后把后缀树上每一个后缀对应位置的匹配数相乘,就可以得到经过lca的路径在原串中的出现次数之和了。

但注意到有可能X和Y位于同一棵子树内,所以还要对每棵子树再求一次来去重。关于如何实现求每个位置的匹配路径数量,我们可以在正反两串的后缀树上打标记,最后下推到叶节点就好了。这么做因为求每个点贡献的时候都要把后缀树扫一遍,所以总的复杂度是\(O(n^2+nm)\)。

再仔细思考一下不难发现暴力2可以通过点分治来优化。因为点分治后,所有分治子树的size和大小为\(O(n\log n)\),所以总的复杂度就是\(O(n \log n+nm)\)。

整合

那么现在我们的瓶颈就在于每次扫后缀树时的\(O(m)\)。

当前的分治子树size较小时,我们暴力扫后缀树显然是一种浪费。那么我们可以怎么做呢?暴力1!

我们不妨设一个阈值B,当分治子树的size不超过B时我们用做法1,不然就用做法2。 显然size超过B的分治子树只有不超过\(O(⌊n/B⌋)\)个,若我们碰到一个size不超过B的子树就退出的话,也可以证明遍历到的size不超过B的子树只有\(O(⌊n/B⌋)\)个。

解一下方程发现,当B是\(√m\)的时候时间复杂度取到最优。 这样总的复杂度就是\(O((n+m)√m)\),就可以AC啦。

如何在后缀树上打标记?

假设我们建出了S的后缀树。 当我们要加入字符串(Z,Y)的时候,因为后缀树本质是一棵所有后缀组成的trie,于是我们可以从后缀树的根开始往下跳。跳到最终点的时候,我们就在这里打一个标记,然后最后再把所有标记下传到叶节点,那么我们就可以知道每个后缀的前缀匹配了多少字符串。这样就知道正反串的后缀的匹配数,把对应的位置乘起来即可。

这题其实还有一个优化,就是在用方法2去重的时候,也应该按照子树的大小来决定用哪一种方法。而我比较懒,所以就直接用了方法2来去做。


参考bztMinamoto的代码。学习了在后缀树上跳状态。

  1. co int N=1e5;
  2. int n,m;
  3. vector<int> e[N];
  4. char str[N],buf[N]; // city string
  5. int vis[N],maxs[N],siz[N],size,root,sqr; // point divide and conquer
  6. ll ans;
  7. struct Suffix_Automaton{
  8. int str[N]; // Eigen String
  9. int last,tot;
  10. Suffix_Automaton() {last=tot=1;}
  11. int ch[N][26],fa[N],len[N],pos[N],ref[N],siz[N]; // ref:out->in
  12. void extend(int c,int po){
  13. int p=last,cur=last=++tot;
  14. len[cur]=len[p]+1,pos[cur]=po,ref[po]=cur,siz[cur]=1;
  15. for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur;
  16. if(!p) fa[cur]=1;
  17. else{
  18. int q=ch[p][c];
  19. if(len[q]==len[p]+1) fa[cur]=q;
  20. else{
  21. int clone=++tot;
  22. memcpy(ch[clone],ch[q],sizeof ch[q]);
  23. fa[clone]=fa[q],len[clone]=len[p]+1,pos[clone]=pos[q];
  24. fa[cur]=fa[q]=clone;
  25. for(;ch[p][c]==q;p=fa[p]) ch[p][c]=clone;
  26. }
  27. }
  28. }
  29. int cnt[N],ord[N],son[N][26];
  30. void build(){
  31. for(int i=1;i<=tot;++i) ++cnt[len[i]];
  32. for(int i=1;i<=m;++i) cnt[i]+=cnt[i-1]; // edit 1: m
  33. for(int i=1;i<=tot;++i) ord[cnt[len[i]]--]=i;
  34. for(int i=tot,p;i;--i){
  35. p=ord[i];
  36. siz[fa[p]]+=siz[p];
  37. son[fa[p]][str[pos[p]-len[fa[p]]]]=p;
  38. }
  39. }
  40. int tag[N];
  41. void mark(int u,int fa,int now,int len){ // now,len is for fa
  42. if(!now) return;
  43. if(len==this->len[now]) now=son[now][::str[u]-'a'];
  44. else if(str[pos[now]-len]!=::str[u]-'a') now=0;
  45. if(!now) return;
  46. ++tag[now];
  47. for(int i=0,v;i<e[u].size();++i){
  48. if(vis[v=e[u][i]]||v==fa) continue;
  49. mark(v,u,now,len+1);
  50. }
  51. }
  52. void push(){
  53. for(int i=1;i<=tot;++i) tag[ord[i]]+=tag[fa[ord[i]]];
  54. }
  55. }sam1,sam2;
  56. void find_root(int u,int fa){
  57. siz[u]=1,maxs[u]=0;
  58. for(int i=0,v;i<e[u].size();++i){
  59. if(vis[v=e[u][i]]||v==fa) continue;
  60. find_root(v,u),siz[u]+=siz[v],maxs[u]=max(maxs[u],siz[v]);
  61. }
  62. maxs[u]=max(maxs[u],size-siz[u]);
  63. if(maxs[u]<maxs[root]) root=u;
  64. }
  65. int num,tmp[N];
  66. void get_shipped(int u,int fa){
  67. tmp[++num]=u;
  68. for(int i=0,v;i<e[u].size();++i){
  69. if(vis[v=e[u][i]]||v==fa) continue;
  70. get_shipped(v,u);
  71. }
  72. }
  73. void brute_force(int u,int fa,int now){
  74. now=sam1.ch[now][str[u]-'a'];
  75. if(!now) return;
  76. ans+=sam1.siz[now];
  77. for(int i=0,v;i<e[u].size();++i){
  78. if(vis[v=e[u][i]]||v==fa) continue;
  79. brute_force(v,u,now);
  80. }
  81. }
  82. void work(int u,int fa,int op){
  83. fill(sam1.tag+1,sam1.tag+sam1.tot+1,0);
  84. fill(sam2.tag+1,sam2.tag+sam2.tot+1,0);
  85. int to=str[fa]-'a';
  86. if(fa) sam1.mark(u,fa,sam1.son[1][to],1),sam2.mark(u,fa,sam2.son[1][to],1);
  87. else sam1.mark(u,fa,1,0),sam2.mark(u,fa,1,0);
  88. sam1.push(),sam2.push();
  89. for(int i=1;i<=m;++i) ans+=(ll)op*sam1.tag[sam1.ref[i]]*sam2.tag[sam2.ref[m-i+1]];
  90. }
  91. void solve(int u){
  92. if(size<=sqr){
  93. num=0,get_shipped(u,0);
  94. for(int i=1;i<=num;++i) brute_force(tmp[i],0,1);
  95. return; // edit 2
  96. }
  97. vis[u]=1,work(u,0,1);
  98. for(int i=0,v;i<e[u].size();++i){
  99. if(vis[v=e[u][i]]) continue;
  100. work(v,u,-1);
  101. }
  102. for(int i=0,v,all=size;i<e[u].size();++i){
  103. if(vis[v=e[u][i]]) continue;
  104. root=0,size=siz[v]<siz[u]?siz[v]:all-siz[u];
  105. find_root(v,u),solve(root);
  106. }
  107. }
  108. int main(){
  109. sqr=sqrt(read(n)),read(m);
  110. for(int i=1,u,v;i<n;++i){
  111. read(u),read(v);
  112. e[u].push_back(v),e[v].push_back(u);
  113. }
  114. scanf("%s",str+1),scanf("%s",buf+1);
  115. for(int i=1;i<=m;++i) sam1.str[i]=buf[i]-'a',sam1.extend(buf[i]-'a',i);
  116. reverse(buf+1,buf+m+1);
  117. for(int i=1;i<=m;++i) sam2.str[i]=buf[i]-'a',sam2.extend(buf[i]-'a',i);
  118. sam1.build(),sam2.build();
  119. root=0,maxs[0]=n,size=n;
  120. find_root(1,0),solve(root);
  121. printf("%lld\n",ans);
  122. return 0;
  123. }

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. [BZOJ1921] [CTSC2010]珠宝商

    Description Input 第一行包含两个整数 N,M,表示城市个数及特征项链的长度. 接下来的N-1 行, 每行两个整数 x,y, 表示城市 x 与城市 y 有直接道路相连.城市由1~N进行 ...

  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. libevent实现TCP 客户端

    ibevent实现Tcp Client基于bufferevent实现 #include <stdio.h> #include <unistd.h> #include <s ...

  2. python3的 基础

    ]print(list(set(lst))) # 面试题: # a = 10 # b = 20 # a,b = b,a      # 10000% # print(b)  # 10 # print(a ...

  3. vue页面params传值的必须传name

    a.vue向b.vue传值 a.vue this.$router.push({ path: '/payType', query: { putUpList: this.putUpList, name:' ...

  4. 【ABAP】第一章-基础

    1. ABAP语法基础 1.1 基本数据类型 C.N.D.T.I.F.P.X.string.Xstring P:默认为8字节,最大允许16字节.最大整数位:16*2 = 32 - 1 = 31 -14 ...

  5. 关于WPF中的XAML

    XAML全称extensible application markup language(可扩展性标记语言) 可扩展应用程序标记语言(XAML)是一种声明性语言.概括来说,就是为应用程序构建UI.目前 ...

  6. 解决Windows10关闭UAC后,开机启动项不生效的问题

    Windows10关闭UAC后,会发现启动项不生效. 运行输入gpedit.msc打开组策略(家庭版没有组策略功能) 依次展开计算机配置->Windows设置->安全设置->本地策略 ...

  7. 2.8_Database Interface ADO由来

    OLE-DB,它无法广为流行,因为如下两点: 1.由于OLE-DB太底层化,使用上非常复杂,需要程序员拥有高潮的技巧. 2.OLEDB标准的API是C++API,只能供C++语言调用. 为了使得流行的 ...

  8. .NET子页Main页面实例(UI页面)

    <%@ Page Language="C#"  MasterPageFile="~/MasterPageDefault.master"   AutoEve ...

  9. Sqlite in flutter, how database assets work

    First off, you will need to construct a sqlite database from your csv. This can be done in the follo ...

  10. 5G能带来什么改变-从鸿蒙OS说起

    背景 从5G投票事件开始,开始关注5G.许多文章都说到5G的特点有速度快.时延低,其中,时延低是最重要的特点.然而,时延低能给社会带来什么改变呢? 2G是短信的时代,3G促成了语音视频,4G促成了短视 ...