【SAM套路/AC自动机+主席树】CF547E Mike and Friends

【题目】
CF
给定 n n n个串 a i a_i ai Q Q Q组询问 a l ∼ a r a_l\sim a_r alar a x a_{x} ax出现了多少次。
∑ ∣ a i ∣ ≤ 2 × 1 0 5 , Q ≤ 5 × 1 0 5 \sum |a_i| \leq 2\times 10^5,Q\leq 5\times 10^5 ai2×105,Q5×105

【解题思路】
这种东西显然 SAM \text{SAM} SAM线段树合并就可以做了吧。建出 SAM \text{SAM} SAM,那么询问就是子树中编号为某段区间 e n d p o s endpos endpos的数量。我们以所在串编号建立线段树,线段树合并 r i g h t right right集合即可。

然而我又忘记SAM是节点数是两倍了

什么?不会 SAM \text{SAM} SAM?没有关系。

其实做这题的初衷就是练 AC \text{AC} AC自动机的,原理一样,但显然不够优秀

我们建出 AC \text{AC} AC自动机以后,再用每个串跑一次,对于当前串 s s s前缀 s i s_i si,在其对应 f a i l fail fail树的位置贡献 + 1 +1 +1,那么这个前缀的所有后缀都会被贡献一次,即它 f a i l fail fail树子树中所有节点对于 s s s这个串的计数都会 + 1 +1 +1

现在问题就是统计子树内代表串编号在 [ l , r ] [l,r] [l,r]的所有节点权值和。
观察到询问编号在 [ l , r ] [l,r] [l,r]这个东西是可以差分的,那么我们线段树的下标显然就不是编号了。一种显然的方式是,我们对 f a i l fail fail树求出它的 d f s dfs dfs序,然后以此为下标建立线段树,可持久化一下就行了。

我们也可以使用离线 B I T BIT BIT的方式解决这个问题,可以获得更优秀的空间复杂度。

两种做法时间复杂度是一样的,都是 O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn)

两种做法都写了一次,明显感觉到了 SAM \text{SAM} SAM在处理某些问题时的优越性以及直观(在入门了以后)。

【参考代码】
SAM + \text{SAM}+ SAM+线段树合并

#include<bits/stdc++.h>
using namespace std;

const int N=4e5+10,M=N*40;
int n,Q;
char s[N];

namespace IO
{
	int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
	void writeln(int x){write(x);putchar('\n');}
}
using namespace IO;

namespace Tree
{
	int rt[N];
	struct Segment
	{
		int sz,ls[M],rs[M],sum[M];
		void update(int &x,int l,int r,int p)
		{
			if(!x) x=++sz; ++sum[x];
			if(l==r) return;
			int mid=(l+r)>>1;
			if(p<=mid) update(ls[x],l,mid,p);
			else update(rs[x],mid+1,r,p);
		}
		int query(int x,int l,int r,int L,int R)
		{
			if(!x) return 0;
			if(L<=l && r<=R) return sum[x];
			int mid=(l+r)>>1,res=0;
			if(L<=mid) res+=query(ls[x],l,mid,L,R);
			if(R>mid) res+=query(rs[x],mid+1,r,L,R);
			return res;
		}
		int merge(int x,int y)
		{
			if(!x || !y) return x+y;
			int z=++sz;
			ls[z]=merge(ls[x],ls[y]);
			rs[z]=merge(rs[x],rs[y]);
			sum[z]=sum[x]+sum[y];
			return z;
		}
	}tr;
}
using namespace Tree;

namespace SAM
{
	int p[N],b[N],c[N],id[N];
	struct SAM
	{
		int sz,las,fa[N],mx[N],ch[N][26];
		void extend(int x)
		{
			int p,q,np,nq;
			if(ch[las][x])
			{
				p=las;q=ch[p][x];
				if(mx[q]==mx[p]+1) {las=q;return;}
				nq=++sz;mx[nq]=mx[p]+1;
				memcpy(ch[nq],ch[q],sizeof(ch[q]));
				fa[nq]=fa[q];fa[q]=nq;
				for(;p && ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
				las=nq;return;
			}
			p=las;np=las=++sz;mx[np]=mx[p]+1;
			for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;
			if(!p) fa[np]=1;
			else
			{
				q=ch[p][x];
				if(mx[q]==mx[p]+1) fa[np]=q;
				else 
				{
					nq=++sz;mx[nq]=mx[p]+1;
					memcpy(ch[nq],ch[q],sizeof(ch[q]));
					fa[nq]=fa[q];fa[np]=fa[q]=nq;
					for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
				}
			}
		}
		void merge()
		{
			for(int i=1;i<=sz;++i) b[mx[i]]++;
			for(int i=1;i<=sz;++i) b[i]+=b[i-1];
			for(int i=sz;i;--i) c[b[mx[i]]--]=i;
			for(int i=sz,x;i>1;--i) x=c[i],rt[fa[x]]=tr.merge(rt[x],rt[fa[x]]); 
		}
	}S;
}
using namespace SAM;

int main()
{
#ifndef ONLINE_JUDGE
	freopen("CF547E.in","r",stdin);
	freopen("CF547E.out","w",stdout);
#endif
	n=read();Q=read();S.sz=S.las=1;
	for(int i=1,l;i<=n;++i) 
	{
		scanf("%s",s+1);l=strlen(s+1);S.las=1;
		for(int j=1;j<=l;++j) S.extend(s[j]-'a'),tr.update(rt[S.las],1,n,i);
		p[i]=S.las;
	}
	S.merge();
	while(Q--)
	{
		int l=read(),r=read(),id=read();
		writeln(tr.query(rt[p[id]],1,n,l,r));
	}
	return 0;
}

AC \text{AC} AC自动机+主席树

#include<bits/stdc++.h>
#define pb push_back
using namespace std;

const int N=2e5+10,M=N*40;
int n,Q;

namespace IO
{
	int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
	void writeln(int x){write(x);putchar('\n');}
}
using namespace IO;

namespace Tree
{
	vector<int>e[N];
	int ind,rt[N],st[N],en[N];
	struct Segment
	{
		int sz,ls[M],rs[M],sum[M];
		void copy(int x,int y){ls[x]=ls[y];rs[x]=rs[y];sum[x]=sum[y];}
		void pushup(int x){sum[x]=sum[ls[x]]+sum[rs[x]];}
		void update(int y,int &x,int l,int r,int p)
		{
			//printf("%d %d %d %d %d\n",y,x,l,r,p);
			x=++sz;copy(x,y);
			if(l==r){sum[x]++;return;}
			int mid=(l+r)>>1;
			if(p<=mid) update(ls[y],ls[x],l,mid,p);
			else update(rs[y],rs[x],mid+1,r,p);
			pushup(x);
			//printf("%d %d %d\n",l,r,sum[x]);
		}
		int query(int y,int x,int l,int r,int L,int R)
		{
			if(L<=l && r<=R) return sum[x]-sum[y];
			int mid=(l+r)>>1,res=0;
			if(L<=mid) res+=query(ls[y],ls[x],l,mid,L,R);
			if(R>mid) res+=query(rs[y],rs[x],mid+1,r,L,R);
			return res;
		}
	}tr;
	void dfs(int x)
	{
		st[x]=++ind;
		for(int i=0;i<(int)e[x].size();++i) dfs(e[x][i]);
		en[x]=ind;
	}
}
using namespace Tree;

namespace ACM
{
	int ed[N],len[N];
	char s[N];
	vector<int>ts[N];
	queue<int>q;
	struct ACM
	{
		int sz,rt,fail[N],ch[N][26];
		void init(){rt=sz=1;}
		void in(int id)
		{
			scanf("%s",s+1);len[id]=strlen(s+1);int now=rt;
			//printf("%d %d\n",id,len[id]);
			for(int i=1;i<=len[id];++i)
			{
				ts[id].pb(s[i]-'a');
				int x=s[i]-'a';
				if(!ch[now][x]) ch[now][x]=++sz;
				now=ch[now][x];
			}
			ed[id]=now;
		}
		void getfail()
		{
			q.push(rt);
			while(!q.empty())
			{
				int x=q.front();q.pop();
				for(int i=0;i<26;++i)
				{
					if(!ch[x][i]) continue;
					int t=fail[x],t1=ch[x][i];
					while(t && !ch[t][i]) t=fail[t];
					fail[t1]=t?ch[t][i]:rt;
					q.push(t1);
				}
			}
			for(int i=1;i<=sz;++i) e[fail[i]].pb(i);
		}
	}S;
}
using namespace ACM;

void build(int y,int &x,int id)
{
	int now=S.rt,las=y;
	//printf("%d %d %d %d\n",id,len[id],st[ed[id]],en[ed[id]]);
	for(int i=0;i<len[id];++i)
	{
		now=S.ch[now][ts[id][i]];
		tr.update(las,x,1,ind,st[now]);las=x;
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("CF547E.in","r",stdin);
	freopen("CF547E.out","w",stdout);
#endif
	n=read();Q=read();S.init();
	for(int i=1;i<=n;++i) S.in(i);
	S.getfail();dfs(S.rt);
	for(int i=1;i<=n;++i) build(rt[i-1],rt[i],i);
	while(Q--)
	{
		int l=read(),r=read(),id=read();
		printf("%d\n",tr.query(rt[l-1],rt[r],1,ind,st[ed[id]],en[ed[id]]));
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值