[hihocoder1457]后缀自动机+拓扑序

本文探讨了在《十进制进行曲大全》中寻找所有不同旋律的和的问题,通过广义后缀自动机和拓扑排序算法实现,将所有可能的旋律视为十进制数并求和,最终结果对10^9+7取模。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

hihocoder1457

描述

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

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

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

分析
  • 首先这是很多个字符串,并且你要考虑的是在整个字符集内本质不同的子串,所以不要想着把每个串都做一遍后缀自动机。
  • 那么这道题就是一个广义后缀自动机了,处理方法就是每次插入完一个串以后,让la=1就好了。(当然你在每两个串中间插入一个特殊字符也是可以的)。
  • 现在就是计算答案了。考虑我们是要把所有本质不同的子串当成十进制数,累加到答案里面。那么应该是在后缀自动机的图上搞事情,和parent树没什么关系。
  • 设sum[i]表示从 t 0 t_0 t0到i这个节点,所有本质不同的子串累加的答案。这个显然要加到答案里面。然后置于如何递推? s u m [ n e [ i ] [ c ] ] + = s u m [ i ] ∗ 10 + c ∗ n u m [ i ] sum[ne[i][c]]+=sum[i]*10+c*num[i] sum[ne[i][c]]+=sum[i]10+cnum[i],其中 n u m [ i ] num[i] num[i]是从 t 0 t_0 t0到i这个点,有多少个本质不同的子串。
  • 显然需要做一个拓扑。然后本题就算做完。
  • 哎,虽然思路很简单,但是我写代码自带bug啊,服了。记得,如果是在两个字符串中间加特殊字符了,那么把所有入度为0的点都先加入队列。如果是每次把la=1,那么最开始只需要把1加入队列就可以。(具体原因自己想一下)。每一个数的范围一定要想清楚,不然你会暴毙的。
Coding
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define ll long long
#define rint register int
using namespace std;
const int N=2e6+100;
const int mod=1e9+7;
ll t,n,tot=1,la=1,siz[N],len[N],fa[N],ne[N][30],A[N],c[N],num[N];
char s[N];ll sum[N],ans;
queue<int>q;int rd[N];
void add(int c){
    rint p=la,np=la=++tot;siz[tot]=1;len[tot]=len[p]+1;
    for(;p&&!ne[p][c];p=fa[p]) ne[p][c]=np;
    if(!p) fa[np]=1;
    else{
        rint q=ne[p][c];
        if(len[p]+1==len[q]) fa[np]=q;
        else{
            rint nq=++tot;
            memcpy(ne[nq],ne[q],sizeof(ne[q]));
            fa[nq]=fa[q];
            fa[q]=fa[np]=nq;
            len[nq]=len[p]+1;
            for(;p&&ne[p][c]==q;p=fa[p]) ne[p][c]=nq;
        }
    }
}
int main(){
	scanf("%lld",&t);
	while(t--){
		scanf("%s",s+1);
		n=strlen(s+1);la=1;
		for(int i=1;i<=n;++i) add(s[i]-'0');
	}
	num[1]=1;
	for(int i=1;i<=tot;++i){
		for(int j=0;j<10;++j) 
		if(ne[i][j])
			rd[ne[i][j]]++;
	}
	for(int i=1;i<=tot;++i) if(!rd[i]) q.push(i);
	//q.push(1);
	while(q.size()){
		int x=q.front();q.pop();
		//cout<<x<<' '<<sum[x]<<endl;
		for(int j=0;j<10;++j){
			int nq=ne[x][j];
			if(!nq) continue;
			num[nq]=(num[x]+num[nq])%mod;rd[nq]--;
			sum[nq]=((sum[nq]+1LL*sum[x]*10%mod)%mod+1LL*num[x]*j%mod)%mod;
			if(!rd[nq]) q.push(nq);
		}
	}
	for(int i=1;i<=tot;++i) ans=(ans+sum[i])%mod;
	//for(int i=1;i<=tot;++i) cout<<num[i]<<endl;
	cout<<ans<<endl;
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值