2020牛客暑期多校训练营(第二场) A All with Pairs AC自动机 fail树上处理

有一个比较显然的思路:

先把每个串插入到字典树中建立AC自动机

枚举每个字符串做为t串,求f(s,t),我们知道AC自动机上fail[i]即:根节点到i节点路径上字符串最长的(当前字符串的后缀)与模式串的前缀匹配。类似于于KMP里的next。所以枚举了一个字符串t,s串一定在t串结尾字符节点u的fail树祖先链上。

我们沿fail树链求贡献即可。

但有个问题:

上图是样例的字典树,红线是fail数组。

绿线枚举t串为aba时,遍历6号节点的fail树链。

贡献为: 在6号节点,有3*3=9

在5号节点有:2*2=4;

在2号节点有1*1*2=2;  我们发现这里就出问题了! 因为2号节点的儿子中包含了aba这个字符串,而这个字符串做为s的前缀与t最长后缀匹配已经在6号节点处理过了。这里会多算。

为了解决这个问题,我们可以记录每个节点属于哪几个字符串。然后枚举t串,找s串时:每计算一个串做为s,就把他vs掉,当前的前缀是于t后缀的最长匹配,后面再遇到这个串不进行计算即可。

最后是复杂度证明:看起来这是n^2的,其实是On*sqrt(n)的:

最坏是一条全部字符一样的链:

a

aa

aaa

……

运行次数:

\sum_{x=1}^{sqrt(m)}x*sqrt(m)-\frac{(x+1)x}{2}

大约是 1e8

实际只跑了200ms,KMP要2000ms

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
const int M = 1e6+7;
const int mod =998244353 ;
int ed[M],pr[M];
ll sm=0;
struct AC
{
	int tr[M][26],flag[M],fail[M],nm[M];
	vector<int>g[M];int vs[M];
	int cnt=1;queue<int>q;
	void in(char *s,int id)
	{
		int len=strlen(s),u=1;
		for(int i=0;i<len;i++)
		{
			int v=s[i]-'a';//注意这里的字符集大小默认为小写字母,根据题目要进行更改!!!
			if(!tr[u][v])tr[u][v]=++cnt;//新建节点
			u=tr[u][v];g[u].pb(id);
			nm[u]++;//包含该节点  共几个字符串
			flag[u]=i+1;//该节点是字符串的第几个节点(从前往后) 
		}
		ed[id]=u;
	}
	void get_fail()
	{
		for(int i=0;i<26;i++)tr[0][i]=1;
		q.push(1);fail[1]=0;
		while(!q.empty())
		{
			int u=q.front();q.pop();
			for(int i=0;i<26;i++)
			{
				int v=tr[u][i];//遍历u所有儿子,这样不同记录fa 
				int Fail=fail[u];//由于BFS遍历,fail[u]已经处理好了,现在是找v的fail
				if(!v)tr[u][i]=tr[Fail][i];//不存在节点v,这样做的目的是,后面fail指针失配时直接不断返回fail 
				else fail[v]=tr[Fail][i],q.push(v);//存在实节点才入队列 
			}
		}
	}
	ll gao(int u)
	{
		vector<int>tp;tp.clear();
		ll ans=0;int k=u;
		while(k>1)
		{
			for(auto x:g[k])if(!vs[x])vs[x]=1,ans=(ans+(ll)flag[k]*flag[k]%mod)%mod,tp.pb(x),sm++;
			k=fail[k];
		}
		for(auto x:tp)vs[x]=0;
		pr[u]=ans;
		return ans;
	}
}ac;
char s[M];
int main()
{
//	freopen("12.in","r",stdin);
  	int n;
  	scanf("%d",&n);
  	ac.init();
  	for(int i=1;i<=n;i++)
  	{
  		scanf("%s",s);
  		ac.in(s,i);
	}
	ac.get_fail();
	ll ans=0;
	for(int i=1;i<=n;i++)
	{
		if(pr[ed[i]])ans=(ans+pr[ed[i]])%mod;//记忆化,防止复杂度退化为n^2 
		else  ans=(ans+ac.gao(ed[i]))%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值