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)就可以了。好了,我写程序去了。

Close

输入

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

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

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

输出

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

Sample Input

2
101
09

Sample Output

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=0,f=1;char ch=getchar();
 15     while(!isdigit(ch)){if(ch==‘-‘)f=-1;ch=getchar();}
 16     while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-‘0‘;ch=getchar();}
 17     return x*f;
 18 }
 19
 20 int n;
 21 struct sam
 22 {
 23     int last,cnt;
 24     int c[N][11],fa[N],mx[N],flag[N];
 25     sam(){last=cnt=1;}
 26     void extend(int x)
 27     {
 28         int p=last,np=last=++cnt;mx[np]=mx[p]+1;
 29         if(x==10)flag[np]=1;
 30         while(p&&!c[p][x])
 31         {
 32             c[p][x]=np;
 33             p=fa[p];
 34         }
 35         if(!p)fa[np]=1;
 36         else
 37         {
 38             int q=c[p][x];
 39             if(mx[q]==mx[p]+1)fa[np]=q;
 40             else
 41             {
 42                 int nq=++cnt;mx[nq]=mx[p]+1;
 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==10)flag[nq]=1;
 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=1;i<=cnt;i++)
 61             for (int j=0;j<=10;j++)
 62                 if(c[i][j])du[c[i][j]]++;
 63         num[1]=1;
 64     }
 65     int now,pre;queue<int>q[2];
 66     ll sum[N];
 67     void solve()
 68     {
 69         pre=0,now=1;
 70         for (int i=1;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]=0;
 78                 for (int i=0;i<=10;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]*10+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=0;
 96         for (int i=1;i<=cnt;i++)
 97             (ans+=sum[i])%=mod;
 98         printf("%lld\n",ans);
 99     }
100 }sam;
101 char s[1000007];
102
103 int main()
104 {
105     n=read();
106     for (int i=1;i<=n;i++)
107     {
108         scanf("%s",s+1);int len=strlen(s+1);
109         for (int j=1;j<=len;j++)
110             sam.extend(s[j]-‘0‘);
111         if(i<n)sam.extend(10);
112     }
113     sam.init();
114     sam.solve();
115 }

原文地址:https://www.cnblogs.com/fengzhiyuan/p/8531028.html

时间: 2024-08-09 08:38:05

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

hihocoder #1457 : 后缀自动机四&#183;重复旋律7

#1457 : 后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 神奇的是小Hi发现了一部名字叫<十进制进行曲大全>的作品集,顾名思义,这部作品集里有许多作品,但是所有的作品有一个共同特征:只用了十个音符,所有的音符都表示成0-9的数字. 现在小Hi想知道这部作品中所有不同的旋律的“和”(也就是把串看成数字,在十进制下的求和,允许有前导0).答案有可能

BZOJ 后缀自动机四&#183;重复旋律7

后缀自动机四·重复旋律7 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 神奇的是小Hi发现了一部名字叫<十进制进行曲大全>的作品集,顾名思义,这部作品集里有许多作品,但是所有的作品有一个共同特征:只用了十个音符,所有的音符都表示成0-9的数字. 现在小Hi想知道这部作品中所有不同的旋律的“和”(也就是把串看成数字,在十进制下的求和,允许有前导0).答案有可能很大,我们需要对

后缀自动机四&#183;重复旋律7

后缀自动机四·重复旋律7 1 #include <bits/stdc++.h> 2 using namespace std; 3 const int maxn = 2000010; 4 const int mod = 1e9 + 7; 5 char s[maxn]; 6 int len[maxn<<1], tr[maxn<<1][11], link[maxn<<1]; 7 int sz, last; 8 void init(){ 9 sz = last =

hihocoder #1449 : 后缀自动机三&#183;重复旋律6

#1449 : 后缀自动机三·重复旋律6 时间限制:15000ms 单点时限:3000ms 内存限制:512MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一个音乐旋律被表示为一段数构成的数列. 现在小Hi想知道一部作品中所有长度为K的旋律中出现次数最多的旋律的出现次数.但是K不是固定的,小Hi想知道对于所有的K的答案. 解题方法提示 × 解题方法提示 小Hi:上次我们已经学习了后缀自动机了,今天我们再来解决一个用到后缀自动机的问题. 小Ho:好!那我们开始吧! 小Hi:现在我们要对K

hihocoder #1465 : 后缀自动机五&#183;重复旋律8

#1465 : 后缀自动机五·重复旋律8 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 小Hi发现旋律可以循环,每次把一段旋律里面最前面一个音换到最后面就成为了原旋律的“循环相似旋律”,还可以对“循环相似旋律”进行相同的变换能继续得到原串的“循环相似旋律”. 小Hi对此产生了浓厚的兴趣,他有若干段旋律,和一部音乐作品.对于每一段旋律,他想知道有多少在音乐作品中的子串(重复便多

【后缀自动机】【拓扑排序】【动态规划】hihocoder1457 后缀自动机四&#183;重复旋律7

解题方法提示 小Hi:我们已经学习了后缀自动机,今天我们再来看这道有意思的题. 小Ho:好!这道题目让我们求的是若干的数字串所有不同子串的和. 小Hi:你能不能结合后缀自动机的性质来思考如何解决本题? 小Ho:这道题目既然是关于子串,那么我知道从后缀自动机的所有状态中包含的子串的集合恰好对应原串的所有不重复子串. 小Hi:很好.那你可以先简化问题,想想只有一个串怎么做? 小Ho:好的.这个难不倒我.我上次已经知道如何计算一个串所有不同子串的数量,现在这题也类似,只不过计算更加复杂一点. 小Hi:

hihocoder 后缀自动机五&#183;重复旋律8 求循环同构串出现的次数

描述 小Hi平时的一大兴趣爱好就是演奏钢琴.我们知道一段音乐旋律可以被表示为一段数构成的数列. 小Hi发现旋律可以循环,每次把一段旋律里面最前面一个音换到最后面就成为了原旋律的“循环相似旋律”,还可以对“循环相似旋律”进行相同的变换能继续得到原串的“循环相似旋律”. 小Hi对此产生了浓厚的兴趣,他有若干段旋律,和一部音乐作品.对于每一段旋律,他想知道有多少在音乐作品中的子串(重复便多次计)和该旋律是“循环相似旋律”. 解题方法提示 × 解题方法提示 小Hi:我们已经对后缀自动机比较熟悉了,今天我

HIHOcoder 1445 后缀自动机二&#183;重复旋律5

思路 题目要求求出有多少个不同的子串出现 因为后缀自动机每个状态存储的是连续的后缀,所以一个状态对应的子串个数就是maxlen[x]-minlen[x]+1 代码 #include <cstdio> #include <algorithm> #include <cstring> #include <iostream> using namespace std; const int MAXN = 1000100*2; int maxlen[MAXN],minle

hihocoder-1419 后缀数组四&#183;重复旋律4 求连续重复次数最多的子串

对于重复次数,如果确定了重复子串的长度len,那重复次数k=lcp(start,start+len)/len+1.而暴力枚举start和len的复杂度是O(n^2),不能接受.而有一个规律,若我们只枚举len的整数倍作为起始,如果将它向前移动小于len位可以构成重复次数更长的串,那么那个位置p=start-len+lcp%len.所以每次我们计算两者并求max再与ans做max即可. #include <cstdio> #include <string> #include <