描述

小Hi平时的一大兴趣爱好就是演奏钢琴。我们知道一段音乐旋律可以被表示为一段数构成的数列。

神奇的是小Hi发现了一部名字叫《十进制进行曲大全》的作品集,顾名思义,这部作品集里有许多作品,但是所有的作品有一个共同特征:只用了十个音符,所有的音符都表示成0-9的数字。

现在小Hi想知道这部作品中所有不同的旋律的“和”(也就是把串看成数字,在十进制下的求和,允许有前导0)。答案有可能很大,我们需要对(10^9 + 7)取摸。

解题方法提示

×

解题方法提示

小Hi:我们已经学习了后缀自动机,今天我们再来看这道有意思的题。

小Ho:好!这道题目让我们求的是若干的数字串所有不同子串的和。

小Hi:你能不能结合后缀自动机的性质来思考如何解决本题?

小Ho:这道题目既然是关于子串,那么我知道从后缀自动机的所有状态中包含的子串的集合恰好对应原串的所有不重复子串。

小Hi:很好。那你可以先简化问题,想想只有一个串怎么做?

小Ho:好的。这个难不倒我。我上次已经知道如何计算一个串所有不同子串的数量,现在这题也类似,只不过计算更加复杂一点。

小Hi:那你可以详细说说。

小Ho:我们举个例子,假设S="1122124",其实就是我们熟悉的例子"aabbabd"啦。

状态 子串 endpos sum
S 空串   0
1 1 {1,2,5} 1
2 11 {2} 11
3 112 {3} 112
4 1122,122,22 {4} 1266
5 2 {3,4,6} 2
6 11221,1221,221,21 {5} 12684
7 112212,12212,2212,212 {6} 126848
8 12 {3,6} 12
9 1122124,122124,22124,2124,124,24,4 {7} 1248648

小Ho:如果我们能像上面的表格一样求出每个状态中包含的子串的"和",不妨记为sum(st)。那么我们只要求出Σsum(st)就是答案了。

小Hi:那你讲讲怎么求出每个状态的和?

小Ho:从初始状态开始一个个递推出来咯。比如我们现在要求状态6也就是{11221,1221,221,21}的和。我们知道到达状态6的边(transition)有2条,分别是trans[4][1]和trans[5][1]。如果我们已经求出sum(4) = 1266, sum(5)=2,那么我们就可以求出sum(6)=(sum(4) * 10 + 1 * |substrings(4)|]) + (sun(5) * 10 + 1 * |substring(5)|) = (12660 + 1 * 3) + (2 * 10 + 1 * 1) = 12684。

小Ho:换句话说,状态6里的{11221, 1221, 221}这三个子串是从状态4的所有(3个)子串乘以10再加1得到的;状态6里的{21}这个子串是从状态5的所有(1个)子串乘以10再加1得到的。也就是说对于状态st

sum(st) = Σ{sum(x) * 10 + c * |substrings(x)| | trans[x][c] = st}。

小Ho:我们知道SAM的状态和转移构成了一个有向无环图,我们只要求出状态的拓扑序,依次求出sum(st)即可。

小Hi:不错嘛。那我们回到原题的多个串的情况,怎么解决?

小Ho:多个串我就不会了 ┑( ̄Д  ̄)┍

小Hi:还记得我们第122周用后缀数组求多个串的最长公共字串时用到的技巧么?

小Ho:把多个串用'#'连接起来当作一个串来处理?

小Hi:没错。这次我们也使用这种方法,把所有串用冒号':' (':'的ACII码是58,也就是'0'的ASCII码+10,方便处理) 连接以来。以两个串"12"和"234"为例,"12:234"的SAM如图:

 '

状态 子串 endpos |valid-substrings| sum
S 空串   1 0
1 1 {1} 1 1
2 12 {2} 1 12
3 12:,2:,: {3} 0 0
4 12:2,2:2,:2 {4} 0 0
5 2 {2,4} 1 2
6 12:23,2:23,:23,23,3 {5} 2 26
7 12:234,2:234,:234,234,34,4 {6} 3 272

小Ho:看上去如果我们把每个状态中带冒号的子串都排除掉,好像也是可以递推的!

小Hi:没错。如果我们用valid-substrings(st)表示一个状态中所有的不带冒号的子串,那么对于sum(st)我们有类似的递推式

sum(st) = Σ{sum(x) * 10 + c * |valid-substrings(x)| | trans[x][c] = st}

小Ho:那么关键就是|valid-substrings(st)|怎么求出来了?

小Hi:没错。|valid-substrings(st)|代表st中不带冒号的子串个数,这个值恰好就是从初始状态S到状态st的所有"不经过冒号转移的边"的路径数目。

小Ho:好像有点绕。

小Hi:举个例子,对于状态6,如果我们不经过标记为':'的转移,那么从S到状态6一共有2条路径,是S->6和S->5->6,分别对应不带冒号的子串3和23。前面已经提到过SAM的状态和转移构成了一个有向无环图,有向无环图上的路径数目也是一个经典的拓扑排序问题,可以参考之前我们的讨论

小Ho:我明白了。建完SAM之后对所有状态拓扑排序,然后按拓扑序递推一边求出|valid-substrings(st)|,一边求出sum(st)就可以了。好了,我写程序去了。

Close

输入

第一行,一个整数N,表示有N部作品。

接下来N行,每行包含一个由数字0-9构成的字符串S。

所有字符串长度和不超过 1000000。

输出

共一行,一个整数,表示答案 mod (10^9 + 7)。

Sample Input

  1. 2
  2. 101
  3. 09

Sample Output

  1. 131
  1. #include<cstring>
  2. #include<cmath>
  3. #include<algorithm>
  4. #include<cstdio>
  5. #include<queue>
  6. #include<iostream>
  7.  
  8. #define ll long long
  9. #define N 3000007
  10. #define mod 1000000007
  11. using namespace std;
  12. inline int read()
  13. {
  14. int x=,f=;char ch=getchar();
  15. while(!isdigit(ch)){if(ch=='-')f=-;ch=getchar();}
  16. while(isdigit(ch)){x=(x<<)+(x<<)+ch-'';ch=getchar();}
  17. return x*f;
  18. }
  19.  
  20. int n;
  21. struct sam
  22. {
  23. int last,cnt;
  24. int c[N][],fa[N],mx[N],flag[N];
  25. sam(){last=cnt=;}
  26. void extend(int x)
  27. {
  28. int p=last,np=last=++cnt;mx[np]=mx[p]+;
  29. if(x==)flag[np]=;
  30. while(p&&!c[p][x])
  31. {
  32. c[p][x]=np;
  33. p=fa[p];
  34. }
  35. if(!p)fa[np]=;
  36. else
  37. {
  38. int q=c[p][x];
  39. if(mx[q]==mx[p]+)fa[np]=q;
  40. else
  41. {
  42. int nq=++cnt;mx[nq]=mx[p]+;
  43. memcpy(c[nq],c[q],sizeof(c[q]));
  44. fa[nq]=fa[q];
  45. fa[q]=fa[np]=nq;
  46. while(c[p][x]==q)c[p][x]=nq,p=fa[p];
  47. if(x==)flag[nq]=;
  48. }
  49. }
  50. }
  51. int du[N],num[N];
  52. void init()
  53. {
  54. /*for (int i=1;i<=cnt;i++)
  55. {
  56. for (int j=0;j<=10;j++)
  57. if(c[i][j])cout<<c[i][j]<<" ";
  58. cout<<endl;
  59. }*/
  60. for (int i=;i<=cnt;i++)
  61. for (int j=;j<=;j++)
  62. if(c[i][j])du[c[i][j]]++;
  63. num[]=;
  64. }
  65. int now,pre;queue<int>q[];
  66. ll sum[N];
  67. void solve()
  68. {
  69. pre=,now=;
  70. for (int i=;i<=cnt;i++)
  71. if(!du[i])q[pre].push(i);
  72. while(!q[pre].empty())
  73. {
  74. while(!q[pre].empty())
  75. {
  76. int x=q[pre].front();q[pre].pop();
  77. if(flag[x])sum[x]=;
  78. for (int i=;i<=;i++)
  79. if(c[x][i])
  80. {
  81. // if(c[x][i]==10)cout<<"fack="<<x<<" "<<c[x][i]<<endl;
  82. if (!flag[x])
  83. {
  84. num[c[x][i]]+=num[x];
  85. (sum[c[x][i]]+=sum[x]*+num[x]*i)%=mod;
  86. }
  87. du[c[x][i]]--;
  88. if(!du[c[x][i]])q[now].push(c[x][i]);
  89. }
  90. }
  91. swap(now,pre);
  92. }
  93. /*for (int i=1;i<=cnt;i++)
  94. cout<<sum[i]<<endl;*/
  95. ll ans=;
  96. for (int i=;i<=cnt;i++)
  97. (ans+=sum[i])%=mod;
  98. printf("%lld\n",ans);
  99. }
  100. }sam;
  101. char s[];
  102.  
  103. int main()
  104. {
  105. n=read();
  106. for (int i=;i<=n;i++)
  107. {
  108. scanf("%s",s+);int len=strlen(s+);
  109. for (int j=;j<=len;j++)
  110. sam.extend(s[j]-'');
  111. if(i<n)sam.extend();
  112. }
  113. sam.init();
  114. sam.solve();
  115. }

hihocoder 1457 后缀自动机四·重复旋律7 求不同子串的和的更多相关文章

  1. HIHOcoder 1457 后缀自动机四·重复旋律7

    思路 后缀自动机题目,题目本质上是要求求出所有不同的子串的和,SAM每个节点中存放的子串互不相同,所以对于每个节点的sum,可以发现是可以递推的,每个点对子节点贡献是sum[x]*10+c*sz[x] ...

  2. hihocoder 1457 后缀自动机四·重复旋律7 ( 多串连接处理技巧 )

    题目链接 分析 : 这道题对于单个串的用 SAM 然后想想怎么维护就行了 但是多个串下.可以先将所有的串用一个不在字符集( 这道题的字符集是 '0' ~ '9' ) 链接起来.建立后缀自动机之后 在统 ...

  3. hihoCoder #1457 : 后缀自动机四·重复旋律7(后缀自动机 + 拓扑排序)

    http://hihocoder.com/problemset/problem/1457 val[i] 表示状态i所表示的所有字符串的十进制之和 ans= ∑ val[i]在后缀自动机上,从起始状态走 ...

  4. hihoCoder.1457.后缀自动机四 重复旋律7(广义后缀自动机)

    题目链接 假设我们知道一个节点表示的子串的和sum,表示的串的个数cnt,那么它会给向数字x转移的节点p贡献 \(sum\times 10+c\times cnt\) 的和. 建广义SAM,按拓扑序正 ...

  5. HDU_1457_后缀自动机四·重复旋律7

    #1457 : 后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成 ...

  6. BZOJ 后缀自动机四·重复旋律7

    后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 神奇的 ...

  7. hihocoder #1419 : 后缀数组四·重复旋律4

    #1419 : 后缀数组四·重复旋律4 时间限制:5000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构 ...

  8. hihoCoder #1445 : 后缀自动机二·重复旋律5

    #1445 : 后缀自动机二·重复旋律5 时间限制:10000ms 单点时限:2000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数 ...

  9. 【后缀自动机】【拓扑排序】【动态规划】hihocoder1457 后缀自动机四·重复旋律7

    解题方法提示 小Hi:我们已经学习了后缀自动机,今天我们再来看这道有意思的题. 小Ho:好!这道题目让我们求的是若干的数字串所有不同子串的和. 小Hi:你能不能结合后缀自动机的性质来思考如何解决本题? ...

随机推荐

  1. java 基础面试

    1. &和&&的区别 答: &是位运算符,表示按位与运算,&&是逻辑运算符,表示逻辑与(and) 2.int 和 Integer 有什么区别 答: Ja ...

  2. 【jQuery】阶段(插入、复制、替换、删除)

    <p>你好!</p> 你最喜欢的水果是? <ul> <li title="苹果">苹果</li> <li titl ...

  3. Gson杂记录

    //Integer userId = getUserId(); //System.out.println("userId:"+userId); /*for(int i=0;i< ...

  4. 散列--数据结构与算法JavaScript描述(8)

    散列 散列是一种常用的数据存储技术,散列后的数据可以快速地插入或取用. 散列使用的数据结构叫做散列表. 在散列表上插入.删除和取用数据都非常快,但是对于查找操作来说却效率低下,比如查找一组数据中的最大 ...

  5. java线程安全总结 - 2 (转载)

    原文地址:http://www.jameswxx.com/java/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E6%80%BB%E7%BB%93%EF%BC%88%E4 ...

  6. Android面试收集录17 Android进程优先级

    在安卓系统中:当系统内存不足时,Android系统将根据进程的优先级选择杀死一些不太重要的进程,优先级低的先杀死.进程优先级从高到低如下. 前台进程 处于正在与用户交互的activity 与前台act ...

  7. Qsys配置生成nios系统模块

    1. 本次使用的是别人写好的例程,主要研究学习,使用quartus 11打开工程 2. bdf文件是块编辑器的,相当于原理图,以前只在用NIOS的时候会用到这种方式.接下来新建一个工程,添加原理图元件 ...

  8. react书写规范小记

    1.对齐方式 //如果没有属性,在自闭和标签前添加一个空格: <Footer /> //如果可以放在一行,放在一行上即可: <Footer bar="bar" / ...

  9. 4.实现简单的shell sed替换功能

    # -*- coding:utf-8 -*- # Author: JACK ZHAO # 程序1: 实现简单的shell sed替换功能 import sys #判断参数个数 if len(sys.a ...

  10. java实现最大堆

    优先队列 普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除.在优先队列中,元素被赋予优先级.当访问元素时,具有最高优先级的元素最先删除.优先队列具有最高级先出 (first in, ...