【BZOJ3473】字符串(广义后缀自动机)

点此看题面

大致题意: 给定(n)个模式串,询问每个模式串有多少子串是至少(k)个模式串的子串。

前言

再也不敢随便偷懒了。。。一开始一看和某题(【BZOJ2780】Sevenk Love Oimaster)非常像,懒得再写一遍,就直接把代码复制过来改了改,然后(WA)了。

后来发现两题还是有些区别的,这道题需要再排序处理一遍信息,然后排序里有一个地方我习惯性地写了一个(n),忘记那份代码里(n)和我平时定义的(n)表示的不是一个含义,就因为这个结果调了好久。。。

广义后缀自动机

其实这道题和那道题的确还是有些相似之处的,至少在前半部分的预处理是一样的。

考虑我们对这(n)个串建后缀自动机,然后对于每一个串,枚举其节点暴力往上跳,给沿途所有点打标记,遇到打过当前串标记的节点就退掉,由于每个串每个点只会被打一次标记,复杂度是正确的。

而显然包含一个点表示的子串的模式串数量就是这个点被打标记的次数。

在这题中,对于每个串我们枚举前缀,然后就是要考虑有多少后缀被打标记次数大于等于(k)

我们可以把所有点按长度排序后从父节点向子节点转移信息,得到转移方程:

[G_p=G_{fa_p}+(l_p-l_{fa_p})[C_pge k] ]

显然就是一个判断当前位置上串能否满足条件,并累加答案的过程。

最终答案就是对每个前缀对应节点(G)值的求和。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define LL long long
using namespace std;
int n,k,tot,l[N+5];char s[N+5];
class SuffixAutomation
{
	private:
		int Nt,lst,p[N<<1],t[N<<1];struct node {LL G;int C,V,L,F,S[30];}O[N<<1];
	public:
		I SuffixAutomation() {Nt=1;}I void Init() {lst=1;}//广义后缀自动机
		I void Ins(CI x,CI ti)//插入新字符
		{
			RI p=lst,now=lst=++Nt;O[now].L=O[p].L+1;
			W(p&&!O[p].S[x]) O[p].S[x]=now,p=O[p].F;if(!p) return (void)(O[now].F=1);
			RI q=O[p].S[x];if(O[p].L+1==O[q].L) return (void)(O[now].F=q);
			RI k=++Nt;(O[k]=O[q]).L=O[p].L+1,O[now].F=O[q].F=k;
			W(p&&O[p].S[x]==q) O[p].S[x]=k,p=O[p].F;
		}
		I void Jump(char *s,CI ti)//预处理
		{
			RI i,p=1,x;for(i=1;i<=l[ti];++i)//枚举点
				{x=p=O[p].S[s[i]&31];W(x&&O[x].V^ti) O[x].V=ti,++O[x].C,x=O[x].F;}//暴力上跳打标记
		}
		I void Work()//统计信息
		{
			RI i,x;for(i=1;i<=Nt;++i) ++t[O[i].L];for(i=1;i<=tot;++i) t[i]+=t[i-1];
			for(i=1;i<=Nt;++i) p[t[O[i].L]--]=i;//排序
			for(i=1;i<=Nt;++i) x=p[i],O[x].G=O[O[x].F].G+(O[x].C>=k?O[x].L-O[O[x].F].L:0);//累加答案
		}
		I void Solve(char *s,CI l)
		{
			RI p=1;LL t=0;for(RI i=1;i<=l;++i) p=O[p].S[s[i]&31],t+=O[p].G;//枚举前缀,统计每个前缀的后缀的答案
			printf("%lld ",t);//输出答案
		}
}S;
int main()
{
	RI i,j;for(scanf("%d%d",&n,&k),i=1;i<=n;++i)//枚举串
	{
		S.Init(),scanf("%s",s+tot+1),l[i]=strlen(s+tot+1);
		for(j=1;j<=l[i];++j) S.Ins(s[tot+j]&31,i);tot+=l[i];//建后缀自动机
	}
	for(tot=0,i=1;i<=n;++i) S.Jump(s+tot,i),tot+=l[i];S.Work();//维护好信息
	for(tot=0,i=1;i<=n;++i) S.Solve(s+tot,l[i]),tot+=l[i];return 0;//枚举串询问
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ3473.html