UOJ#395. 【NOI2018】你的名字(SAM)

传送门

题解:
线段树维护fail树,然后t串暴力在s串fail树上跳,对每个后缀求出重合最多的一部分,然后自己再求一次即可。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

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

const int N=1e6+50;

struct SAM {
	char ch[N];
	int len,last,tot;
	int pos[N],fail[N],son[N][26],l[N];
	inline void extend(int i,int c) {
		int p=pos[i]=++tot; l[p]=l[last]+1;
		while(p && !son[last][c]) son[last][c]=p, last=fail[last];
		if(!last) fail[p]=1;
		else {
			int q=son[last][c];
			if(l[q]==l[last]+1) fail[p]=q;
			else {
				int np=++tot; memcpy(son[np],son[q],sizeof(son[np]));
				fail[np]=fail[q]; fail[q]=fail[p]=np; l[np]=l[last]+1;
				while(last && son[last][c]==q) son[last][c]=np, last=fail[last];
			} 
		} last=p;
	}
	inline void init() {
		for(int i=1;i<=tot;i++) memset(son[i],0,sizeof(son[i])), pos[i]=fail[i]=l[i]=0;
		last=tot=1;
		scanf("%s",ch+1); len=strlen(ch+1);
		for(int i=1;i<=len;i++) extend(i,ch[i]-'a');
	}
} s,t;

int rt[N],lc[N*30],rc[N*30],mx[N*30],tot;
inline void inc(int &k,int l,int r,int p) {
	if(!k) k=++tot;
	if(l==r) {mx[k]=p; return;}
	int mid=(l+r)>>1;
	(p<=mid) ? inc(lc[k],l,mid,p) : inc(rc[k],mid+1,r,p);
	mx[k]=max(mx[lc[k]],mx[rc[k]]);
}
inline int ask(int k,int l,int r,int L,int R) {
	if(!k) return 0; 
	if(L<=l && r<=R) return mx[k];
	int mid=(l+r)>>1;
	if(R<=mid) return ask(lc[k],l,mid,L,R);
	else if(L>mid) return ask(rc[k],mid+1,r,L,R);
	else return max(ask(lc[k],l,mid,L,R),ask(rc[k],mid+1,r,L,R));
}
inline int merge(int x,int y) {
	if(!x) return y;
	if(!y) return x;
	int z=++tot;
	lc[z]=merge(lc[x],lc[y]);
	rc[z]=merge(rc[x],rc[y]);
	mx[z]=max(mx[lc[z]],mx[rc[z]]);
	return z;
}
inline void build_ST() {
	static int c[N],q[N]; 
	for(int i=1;i<=s.tot;i++) c[s.l[i]]++;
	for(int i=1;i<=s.len;i++) c[i]+=c[i-1];
	for(int i=1;i<=s.tot;i++) q[c[s.l[i]]--]=i;
	for(int i=1;i<=s.len;i++) inc(rt[s.pos[i]],1,s.len,i);
	for(int i=s.tot;i>=1;i--) if(s.fail[q[i]]) rt[s.fail[q[i]]]=merge(rt[s.fail[q[i]]],rt[q[i]]);
}

inline void solve() {
	static int cov[N],len[N];
	t.init(); 
	for(int i=1;i<=t.len;i++) len[i]=0;
	for(int i=1;i<=t.tot;i++) cov[i]=0;
	
	for(int i=1;i<=t.len;i++) {
		int p=t.pos[i];
		while(p && !cov[p]) cov[p]=t.l[p], p=t.fail[p];
		if(p) len[i]=cov[p];
	}
	
	int l,r; scanf("%d%d",&l,&r);
	int p=1, mx=0;
	
	#define mn(x) (s.l[s.fail[x]]+1)
	for(int i=1;i<=t.len;i++) {
		int c=t.ch[i]-'a';
		while(p) {
			int lst=ask(rt[s.son[p][c]],1,s.len,l,r);
			if(lst>=l+mn(s.son[p][c])-1) {
				mx=min(mx+1,lst-l+1);
				p=s.son[p][c];
				break;
			} else p=s.fail[p], mx=s.l[p];
		}
		len[i]=max(len[i],mx);
		if(!p) p=1, mx=0;
	}
	
	long long ans=0;
	for(int i=1;i<=t.len;i++) ans+=i-len[i];
	printf("%lld\n",ans);
}
 
int main() {
	s.init(); build_ST();
	int T; scanf("%d",&T);
	while(T--) solve();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值