hihocoder 1457 后缀自动机四·重复旋律7 求不同子串的和
描述
小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)就可以了。好了,我写程序去了。
输入
第一行,一个整数N,表示有N部作品。
接下来N行,每行包含一个由数字0-9构成的字符串S。
所有字符串长度和不超过 1000000。
输出
共一行,一个整数,表示答案 mod (10^9 + 7)。
Sample Input
2
101
09
Sample Output
131
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<iostream> #define ll long long
#define N 3000007
#define mod 1000000007
using namespace std;
inline int read()
{
int x=,f=;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-;ch=getchar();}
while(isdigit(ch)){x=(x<<)+(x<<)+ch-'';ch=getchar();}
return x*f;
} int n;
struct sam
{
int last,cnt;
int c[N][],fa[N],mx[N],flag[N];
sam(){last=cnt=;}
void extend(int x)
{
int p=last,np=last=++cnt;mx[np]=mx[p]+;
if(x==)flag[np]=;
while(p&&!c[p][x])
{
c[p][x]=np;
p=fa[p];
}
if(!p)fa[np]=;
else
{
int q=c[p][x];
if(mx[q]==mx[p]+)fa[np]=q;
else
{
int nq=++cnt;mx[nq]=mx[p]+;
memcpy(c[nq],c[q],sizeof(c[q]));
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
while(c[p][x]==q)c[p][x]=nq,p=fa[p];
if(x==)flag[nq]=;
}
}
}
int du[N],num[N];
void init()
{
/*for (int i=1;i<=cnt;i++)
{
for (int j=0;j<=10;j++)
if(c[i][j])cout<<c[i][j]<<" ";
cout<<endl;
}*/
for (int i=;i<=cnt;i++)
for (int j=;j<=;j++)
if(c[i][j])du[c[i][j]]++;
num[]=;
}
int now,pre;queue<int>q[];
ll sum[N];
void solve()
{
pre=,now=;
for (int i=;i<=cnt;i++)
if(!du[i])q[pre].push(i);
while(!q[pre].empty())
{
while(!q[pre].empty())
{
int x=q[pre].front();q[pre].pop();
if(flag[x])sum[x]=;
for (int i=;i<=;i++)
if(c[x][i])
{
// if(c[x][i]==10)cout<<"fack="<<x<<" "<<c[x][i]<<endl;
if (!flag[x])
{
num[c[x][i]]+=num[x];
(sum[c[x][i]]+=sum[x]*+num[x]*i)%=mod;
}
du[c[x][i]]--;
if(!du[c[x][i]])q[now].push(c[x][i]);
}
}
swap(now,pre);
}
/*for (int i=1;i<=cnt;i++)
cout<<sum[i]<<endl;*/
ll ans=;
for (int i=;i<=cnt;i++)
(ans+=sum[i])%=mod;
printf("%lld\n",ans);
}
}sam;
char s[]; int main()
{
n=read();
for (int i=;i<=n;i++)
{
scanf("%s",s+);int len=strlen(s+);
for (int j=;j<=len;j++)
sam.extend(s[j]-'');
if(i<n)sam.extend();
}
sam.init();
sam.solve();
}
hihocoder 1457 后缀自动机四·重复旋律7 求不同子串的和的更多相关文章
- HIHOcoder 1457 后缀自动机四·重复旋律7
思路 后缀自动机题目,题目本质上是要求求出所有不同的子串的和,SAM每个节点中存放的子串互不相同,所以对于每个节点的sum,可以发现是可以递推的,每个点对子节点贡献是sum[x]*10+c*sz[x] ...
- hihocoder 1457 后缀自动机四·重复旋律7 ( 多串连接处理技巧 )
题目链接 分析 : 这道题对于单个串的用 SAM 然后想想怎么维护就行了 但是多个串下.可以先将所有的串用一个不在字符集( 这道题的字符集是 '0' ~ '9' ) 链接起来.建立后缀自动机之后 在统 ...
- hihoCoder #1457 : 后缀自动机四·重复旋律7(后缀自动机 + 拓扑排序)
http://hihocoder.com/problemset/problem/1457 val[i] 表示状态i所表示的所有字符串的十进制之和 ans= ∑ val[i]在后缀自动机上,从起始状态走 ...
- hihoCoder.1457.后缀自动机四 重复旋律7(广义后缀自动机)
题目链接 假设我们知道一个节点表示的子串的和sum,表示的串的个数cnt,那么它会给向数字x转移的节点p贡献 \(sum\times 10+c\times cnt\) 的和. 建广义SAM,按拓扑序正 ...
- HDU_1457_后缀自动机四·重复旋律7
#1457 : 后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成 ...
- BZOJ 后缀自动机四·重复旋律7
后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 神奇的 ...
- hihocoder #1419 : 后缀数组四·重复旋律4
#1419 : 后缀数组四·重复旋律4 时间限制:5000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为长度为 N 的数构 ...
- hihoCoder #1445 : 后缀自动机二·重复旋律5
#1445 : 后缀自动机二·重复旋律5 时间限制:10000ms 单点时限:2000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数 ...
- 【后缀自动机】【拓扑排序】【动态规划】hihocoder1457 后缀自动机四·重复旋律7
解题方法提示 小Hi:我们已经学习了后缀自动机,今天我们再来看这道有意思的题. 小Ho:好!这道题目让我们求的是若干的数字串所有不同子串的和. 小Hi:你能不能结合后缀自动机的性质来思考如何解决本题? ...
随机推荐
- cmd中编译java脚本 (2013-05-02-bd 写的日志迁移
此前提是已经搭建好了jdk的编译环境! 先写一个java脚本如:建立一个HelloWord.java //public : 表示此类是公共的 一个java文件中只能有一个public类 //class ...
- Git版本控制使用方法入门教程
1. 概述 对于软件版本管理工具,酷讯决定摒弃CVS而转向Git了. 为什么要选择Git? 你真正学会使用Git时, 你就会觉得这个问题的回答是非常自然的.然而当真正需要用文字来回答时,却觉得文字好像 ...
- jira安装说明
阅读目录 1.1 jira说明 1.2 安装配置jira 1.3 web界面访问 1.4 创建第一个项目 1.5 参考文献 回到顶部 1.1 jira说明 JIRA是Atlassian公司出品的项目与 ...
- 使用perl发邮件
如果你使用的是 window 系统,没有 sendmail 工具.这时你就可以使用 perl 的 MIME:Lite 模块作为邮件客户端来发送邮件. 这里我们直接用 cpan 来安装(需要 root ...
- 笔记-python-lib-contextlib
笔记-python-lib-contextlib 1. contextlib with 语句很好用,但不想每次都写__enter_-和__exit__方法: py标准库也为此提供了工具模块c ...
- salt 通信及其安全
salt 通信及其安全 模型架构 server-agent通信模型: server就是salt master; agent就是salt-minion salt也可以作为一个单点服务器管理工具使用,或者 ...
- 剑指Offer - 九度1513 - 二进制中1的个数
剑指Offer - 九度1513 - 二进制中1的个数2013-11-29 23:35 题目描述: 输入一个整数,输出该数二进制表示中1的个数.其中负数用补码表示. 输入: 输入可能包含多个测试样例. ...
- Git上手:Git扫盲区
Git 自述Git 是由伟大的电脑程序员Linus Torvalds编写的一个开源的,分布式的版本控制系统软件. Git 核心原理Git 利用底层数据结构,通过指向索引对象的可变指针,保存文件快照. ...
- Percona-Tookit工具包之pt-slave-find
Preface If we want to check out how many slaves in our replication environment.We can execut ...
- 【Python】python内置函数、列表生成式、生成器
一.内置函数 1 print(all([1,2,3,4]))#判断可迭代的对象里面的值是否都为真 2 print(any([0,1,2,3,4]))#判断可迭代的对象里面的值是否有一个为真 3 pri ...