【NOI2018】你的名字(SAM)(线段树合并)

传送门


题解:

反正空间有1.22个G,xjb乱开也不会MLE

题意简述:

给你一个串 S S S,每次询问给出串 T T T 和区间 l , r l,r l,r,请你回答 T T T有多少个本质不同的子串没有在 S [ l : r ] S[l:r] S[l:r] 中出现。

我们如果能够求出 T T T 每个前缀在 S [ l : r ] S[l:r] S[l:r] 中的最长匹配长度问题就解决了。

首先考虑 l = 1 , r = ∣ S ∣ l=1,r=|S| l=1,r=S 的情况,对 S S S 建立SAM,把 T T T 放上去跑,得到 T T T 每一个前缀的最长匹配长度,对 T T T 建立SAM,每个节点代表的串的最长的能够在 S S S 中匹配的长度就是 r i g h t right right 集合中随便一个点的前缀在 S S S中的最长出现长度,在SAM上把答案算出来就行了。

现在考虑 l , r l,r l,r 任意的情况,现在我们需要求出 T T T 的每个前缀在 S [ l : r ] S[l:r] S[l:r] 的最长出现后缀长度。问题其实就是是否匹配位置的 r i g h t right right 集合中有一个能够完全放到 S [ l : r ] S[l:r] S[l:r] 中,线段树合并维护一下 r i g h t right right 集合,失配的时候暴力将匹配长度 − 1 -1 1 然后看能不能跳到父亲,复杂度是对的。


代码:

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

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

cs int N=1e6+7; 

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

namespace SGT{
	cs int N=::N*40;
	int lc[N],rc[N],tot;
	inline int _copy(int u){++tot;lc[tot]=lc[u],rc[tot]=rc[u];return tot;}
	inline void ins(int &u,int l,int r,int p){
		u=++tot;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]);
	}
	inline bool query(int u,int l,int r,int ql,int qr){
		if(!u||qr<l||r<ql)return false;
		if(ql<=l&&r<=qr)return true;int mid=l+r>>1;
		return query(lc[u],l,mid,ql,qr)||query(rc[u],mid+1,r,ql,qr);
	}
}

namespace S{
	cs int N=::N<<1|1;
	int son[N][26],fa[N],len[N],now,root;
	inline void init(int n){root=now=n+1;}
	inline void push_back(int i,int c){
		int cur=i,p=(i-1)?(i-1):root;
		len[cur]=len[p]+1;
		for(;p&&!son[p][c];p=fa[p])son[p][c]=cur;
		if(!p)fa[cur]=root;
		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],nd[N],rt[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);
		for(int re i=now;i>1;--i){int u=nd[i];SGT::merge(rt[fa[u]],rt[u]);}
	}
	inline void match(int &u,int &nl,cs int l,cs int r,cs int c){
		while(true){
			if(son[u][c]&&SGT::query(rt[son[u][c]],1,n,l+nl,r)){
				++nl;u=son[u][c];return ;
			}
			if(!nl)return ;
			if(--nl==len[fa[u]])u=fa[u];
		}
	}
}

int vl[N];
namespace T{
	cs int N=::N<<1|1;
	int son[N][26],fa[N],len[N],ps[N],now,root;
	inline void clear(){
		memset(son,0,sizeof(int)*(now+1)*26);
	}
	inline void init(int n){root=now=n+1;fa[root]=len[root]=0;}
	inline void push_back(int i,int c){
		int cur=i,p=(i-1)?(i-1):root;
		ps[cur]=len[cur]=len[p]+1;
		for(;p&&!son[p][c];p=fa[p])son[p][c]=cur;
		if(!p)fa[cur]=root;
		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]);
			ps[nq]=ps[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;
		}
	}
	inline ll calc(){
		ll ans=0;for(int re i=1;i<=now;++i)
		ans+=std::max(0,len[i]-std::max(len[fa[i]],vl[ps[i]]));
		return ans;
	}
}

inline void solve(){
	int l,r;scanf("%s%d%d",s+1,&l,&r);
	len=strlen(s+1);T::clear();T::init(len);
	int u=S::root;
	for(int re i=1;i<=len;++i){
		vl[i]=vl[i-1];
		S::match(u,vl[i],l,r,s[i]-'a');
		T::push_back(i,s[i]-'a');
	}
	cout<<T::calc()<<"\n";
}

signed main(){
#ifdef zxyoi
	freopen("name.in","r",stdin);
#endif
	scanf("%s",s+1);len=n=strlen(s+1);S::init(len);
	for(int re i=1;i<=len;++i)S::push_back(i,s[i]-'a');
	S::build();scanf("%d",&m);
	while(m--)solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值