【BZOJ3413】匹配(SAM)(线段树合并)

传送门


这道题的题意没有歧义,但是说得不是很清楚。

简要叙述如下:给你一个母串 S S S,每次给一个串 T T T。现在要寻找 T T T S S S中出现的位置,枚举匹配位置在 S S S中的开头 1 → ∣ S ∣ 1\rightarrow|S| 1S,然后从 T T T中一位一位进行匹配,如果失配跳到下一个开头继续匹配,如果 ∣ T ∣ |T| T完全匹配,则停止,如果所有开头位置都枚举过了,也停止,问进行字符比较的次数。

题解:

对于S建立后缀自动机,每个节点维护right集合中的最小值,则我们直接拿 T T T上去跑一趟可以知道第一次匹配上的位置。接下来需要计算在此之前失配的匹配花费的多少次,刚好失配的那一次可以直接算,其他的就是一个LCP。

再把T扔到SAM上进行匹配,我们考虑在匹配上之前 S S S有多少个位置和 T T T的LCP长度达到了当前匹配长度即可,线段树合并维护right集合大小。

都是常见套路。。。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define cs const

namespace IO{
	inline char gc(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	inline int get_s(char *s){
		int len=0;char c;
		while(isspace(c=gc()));
		while(s[len++]=c,!isspace(c=gc())&&c!=EOF);
		s[len]='\0';return len;
	}
	
	template<typename T>
	inline T get(){
		char c;T num;
		while(!isdigit(c=gc()));num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int gi(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=1e5+7;

int n,m;
char s[N];int len;

namespace SGT{
	cs int N=::N*40;
	int lc[N],rc[N],siz[N],tot;
	inline int _copy(int u){++tot;lc[tot]=lc[u],rc[tot]=rc[u],siz[tot]=siz[u];return tot;}
	inline void ins(int &u,int l,int r,int p){
		u=++tot;siz[u]=1;if(l==r)return ;int mid=l+r>>1;
		(p<=mid)?ins(lc[u],l,mid,p):ins(rc[u],mid+1,r,p);
	}
	inline void merge(int &u,int v){
		if(!u||!v){u|=v;return ;}
		u=_copy(u);
		merge(lc[u],lc[v]);
		merge(rc[u],rc[v]);
		siz[u]=siz[lc[u]]+siz[rc[u]];
	}
	inline int query(int u,int l,int r,int ql,int qr){
		if(!u)return 0;
		if(ql<=l&&r<=qr)return siz[u];
		int mid=l+r>>1;
		if(qr<=mid)return query(lc[u],l,mid,ql,qr);
		if(mid<ql)return query(rc[u],mid+1,r,ql,qr);
		return query(lc[u],l,mid,ql,qr)+query(rc[u],mid+1,r,ql,qr);
	}
}

namespace SAM{
	cs int N=::N<<1;
	int son[N][10],fa[N],len[N],mn[N],now;
	inline void push_back(int i,int c){
		int cur=i,p=(i-1)?(i-1):(n+1);
		len[cur]=len[p]+1;
		for(;p&&!son[p][c];p=fa[p])son[p][c]=cur;
		if(!p)fa[cur]=n+1;
		else if(len[son[p][c]]==len[p]+1)fa[cur]=son[p][c];
		else { 
			int nq=++now,q=son[p][c];
			memcpy(son[nq],son[q],sizeof son[q]);
			len[nq]=len[p]+1;fa[nq]=fa[q];
			fa[q]=fa[cur]=nq;
			for(;p&&son[p][c]==q;p=fa[p])son[p][c]=nq;
		}
	}
	int bin[N],rt[N],nd[N];
	inline void build(){
		for(int re i=1;i<=now;++i)++bin[len[i]];
		for(int re i=1;i<=n;++i)bin[i]+=bin[i-1];
		for(int re i=1;i<=now;++i)nd[bin[len[i]]--]=i;
		for(int re i=1;i<=n;++i)SGT::ins(rt[i],1,n,i),mn[i]=i;
		for(int re i=n+1;i<=now;++i)mn[i]=1e9;
		for(int re i=now;i;--i){int u=nd[i];
			SGT::merge(rt[fa[u]],rt[u]);
			mn[fa[u]]=std::min(mn[fa[u]],mn[u]);
		}
	}
	inline int match(){
		int u=n+1;using ::len;
		for(int re i=1;i<=len;++i){
			int c=s[i]^48;
			if(!son[u][c])return -1;
			u=son[u][c];
		}return mn[u];
	}
	inline void solve(){using ::len;
		len=get_s(s+1);
		int p=match(),ans=~p?(p-len):n,u=n+1;
		for(int re i=1;i<=len;++i){
			int c=s[i]^48;
			if(!son[u][c])break;
			u=son[u][c];
			ans+=SGT::query(rt[u],1,n,1,~p?p-len+i:n);
		}
		cout<<ans<<"\n";
	}
}

signed main(){
#ifdef zxyoi
	freopen("match.in","r",stdin);
#endif
	n=gi();len=get_s(s+1);SAM::now=n+1;
	for(int re i=1;i<=n;++i)SAM::push_back(i,s[i]^48);
	SAM::build();m=gi();
	while(m--)SAM::solve();
	return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值