bzoj 1396: 识别子串 (后缀自动机+线段树)

1396: 识别子串

Time Limit: 10 Sec   Memory Limit: 162 MB
Submit: 308   Solved: 190
[ Submit][ Status][ Discuss]

Description

Input

一行,一个由小写字母组成的字符串S,长度不超过10^5

Output

L行,每行一个整数,第i行的数据表示关于S的第i个元素的最短识别子串有多长.

Sample Input

agoodcookcooksgoodfood

Sample Output

1
2
3
3
2
2
3
3
2
2
3
3
2
1
2
3
3
2
1
2
3
4

HINT

Source

[ Submit][ Status][ Discuss]

题解:后缀自动机+线段树

这道题可以用来更新答案的状态一定的是|right|的大小等于1的状态,每个节点都对应着一些长度为[l[fa]+1,l[x]]的子串.我们按照拓扑序更新,并且需要维护right集合中右端点的位置st[x],然后对于每个|right|=1的状态,对于原串中st[x]-l[fa],st[x]位置都可以用l[fa]+1来更新最小值,如果只是这么做的话,我们发现更新到的位置十分有限,但是我们无法对于[l[fa]+1,l[x]]中的长度都进行更新。于是考虑什么情况是只用上面的方法更新不到的。

举例说明bbbc 对于第三个位置答案应该是bc,但是c在更新的时候只用1更新了自己,而第三个b在更新的时候用3更新了[1,3]这个区间。对于这种情况,我们用线段树再维护一个值,就是能更新到这个点的离他最近的点的位置,即用st[x]更新[st[x]-l[x]+1,st[x]-l[fa]-1]最后计算出距离更新答案即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 200003
using namespace std;
int fa[N],ch[N][30],l[N],pos[N],rt[N],v[N],n,inf;
int p,np,nq,q,last,root,cnt,delta[N*4],tr[N*4],st[N];
char s[N];
void extend(int x)
{
	int c=s[x]-'a';
	p=last; np=last=++cnt;
	l[np]=l[p]+1;
	for (;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
	if (!p) fa[np]=root;
	else{
		q=ch[p][c];
		if (l[p]+1==l[q]) fa[np]=q;
		else {
			nq=++cnt; l[nq]=l[p]+1;
			memcpy(ch[nq],ch[q],sizeof(ch[nq]));
			fa[nq]=fa[q];
			fa[q]=fa[np]=nq;
			for (;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
		}
	}
}
void pushdown(int now)
{
	if (delta[now]!=inf) {
		tr[now<<1]=min(tr[now<<1],delta[now]);
		tr[now<<1|1]=min(tr[now<<1|1],delta[now]);
		delta[now<<1]=min(delta[now<<1],delta[now]);
		delta[now<<1|1]=min(delta[now<<1|1],delta[now]);
		delta[now]=inf;
	}
}
void qjchange(int now,int l,int r,int ll,int rr,int v)
{
	if (ll<=l&&r<=rr) {
		tr[now]=min(tr[now],v);
		delta[now]=min(delta[now],v);
		return;
	}
	int mid=(l+r)/2;
	pushdown(now);
	if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,v);
	if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,v);
}
int find(int now,int l,int r,int x)
{
	if (l==r) return tr[now];
	int mid=(l+r)/2;
	pushdown(now);
	if (x<=mid) return find(now<<1,l,mid,x);
	else return find(now<<1|1,mid+1,r,x);
}
namespace ac{
	int delta[N*4],tr[N*4];
	void clear()
	{
		memset(delta,127,sizeof(delta));
		memset(tr,127,sizeof(tr));
	}
	void pushdown(int now)
	{
		if (delta[now]!=inf) {
			tr[now<<1]=min(tr[now<<1],delta[now]);
			tr[now<<1|1]=min(tr[now<<1|1],delta[now]);
			delta[now<<1]=min(delta[now<<1],delta[now]);
			delta[now<<1|1]=min(delta[now<<1|1],delta[now]);
			delta[now]=inf;
		}
	}
	void qjchange(int now,int l,int r,int ll,int rr,int v)
	{
		if (ll<1||rr<1) return;
		if (ll<=l&&r<=rr) {
			tr[now]=min(tr[now],v);
			delta[now]=min(delta[now],v);
			return;
		}
		int mid=(l+r)/2;
		pushdown(now);
		if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,v);
		if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,v);
	}
	int find(int now,int l,int r,int x)
	{
		if (l==r) return tr[now];
		int mid=(l+r)/2;
		pushdown(now);
		if (x<=mid) return find(now<<1,l,mid,x);
		else return find(now<<1|1,mid+1,r,x);
	}
}
int main()
{
	freopen("a.in","r",stdin);
	freopen("my.out","w",stdout);
	scanf("%s",s+1);
	n=strlen(s+1);
	root=last=++cnt;
	for (int i=1;i<=n;i++) 
	 extend(i);
	for (int i=1;i<=cnt;i++) v[l[i]]++;
	for (int i=1;i<=n;i++) v[i]+=v[i-1];
	for (int i=cnt;i>=1;i--) pos[v[l[i]]--]=i;
	p=root;
	for (int i=1;i<=n;i++) {
		int c=s[i]-'a';
		p=ch[p][c]; rt[p]=1; st[p]=i;
		//cout<<c<<" "<<p<<endl;
	}
	memset(tr,127,sizeof(tr));
	memset(delta,127,sizeof(delta));
	ac::clear();
	inf=tr[0];
	for (int i=cnt;i>=1;i--) {
		int mn=l[fa[pos[i]]]+1; int mn1=l[pos[i]];
		rt[fa[pos[i]]]+=rt[pos[i]];
		st[fa[pos[i]]]=st[pos[i]];
		//cout<<st[pos[i]]<<" "<<l[pos[i]]<<" "<<l[fa[pos[i]]]+1<<endl;
		if(rt[pos[i]]==1){
		  qjchange(1,1,n,st[pos[i]]-mn+1,st[pos[i]],mn);
		  ac::qjchange(1,1,n,max(1,st[pos[i]]-mn1+1),st[pos[i]]-mn,st[pos[i]]);
	    }
	}
	for (int i=1;i<=n;i++) {
		int t=find(1,1,n,i);
		int t1=ac::find(1,1,n,i);
		if (t1!=inf) t=min(t,t1-i+1);
		if (t!=inf) printf("%d\n",t);
		else printf("0\n");
	}
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值