#6071.「2017 山东一轮集训 Day5」字符串(合并n张SAM的DAG)

LINK

如果只有一个串,那么建立 S A M SAM SAM直接求本质不同的子串个数就好了

可以选择利用 p a r e n t parent parent树性质求,也可以选择在DAG d p dp dp不同路径的方案数

现在有多个串,不好利用 S A M SAM SAM的形式:每条路径代表的子串不同(可自动去重)

考虑对每个串都建立了一个 S A M SAM SAM,每个串都有一个DAG

设串 A A A有子串a,ab,abc,空,串 B B B有子串c,空

那么 a b + c ab+c ab+c也是 a b c abc abc,而 a b c + 空 abc+空 abc+也是 a b c abc abc,如何去重??

我们规定,如果串 A A A中走到节点 k k k有到字母 c c c的转移,那我们就在本串转移

不跳转到下一个串去。因为如果跳过去,就和在本串转移形成重复。

那么做法就呼之欲出了

如果一个节点 k k k没有到字母 c c c的边,让节点 k k k指向下一个串DAG根节点下的字母 c c c

这样形成的,把 n n n D A G DAG DAG合并成一张 D A G DAG DAG的图

在上面统计路径条数就好了

这个DAG无敌。

只有确认了在当前串不会产生重复,才会转移到下一个串

#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e6+10;
const int mod = 1e9+7;
int n,f[maxn];
int zi[maxn][27],fa[maxn],len[maxn],id,las,rt[maxn];
char a[maxn];
void insert(int c,int r)
{
	int p = las, np = ++id; las = id;
	len[np] = len[p]+1;
	for( ; p&&zi[p][c]==0 ; p=fa[p] )	zi[p][c] = np;
	if( p==0 )	fa[np] = rt[r];
	else
	{
		int q = zi[p][c];
		if( len[q]==len[p]+1 )	fa[np] = q;
		else
		{
			int nq = ++id; 
			memcpy( zi[nq],zi[q],sizeof zi[nq] );
			fa[nq] = fa[q], len[nq] = len[p]+1;
			fa[np] = fa[q] = nq;
			for( ; p&&zi[p][c]==q ; p = fa[p] )	zi[p][c] = nq;
		}
	}
}
int dfs(int u)
{
	if( f[u] )	return f[u];
	f[u] = 1;
	for(int i=0;i<=25;i++)
		if( zi[u][i] )	f[u] = ( 1ll*f[u]+dfs( zi[u][i] ) )%mod;
	return f[u];
}
signed main()
{
	cin >> n;
	for(int i=1;i<=n;i++)
	{
		scanf("%s",a+1 );
		rt[i] = ++id, las = id;//新的SAM图了 
		int le = strlen( a+1 );
		for(int j=1;j<=le;j++)	insert( a[j]-'a',i );
	}
	for(int i=n-1;i>=1;i--)
	{
		for(int j=rt[i];j<rt[i+1];j++)//枚举第i个SAM的每一个节点
		for(int k=0;k<=25;k++)//枚举每个字母的出边
			if( zi[j][k]==0 )	zi[j][k] = zi[rt[i+1]][k];
	}
	cout << dfs(1);//从第一个SAM的DAG开始DP 
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值