1037. H. Security(贪心+后缀自动机上线段树合并)

本文介绍了一种利用后缀自动机(SAM)解决字符串查找问题的方法,通过建立SAM并维护每个节点的endpos集合,避免了每次查询都需要重新构建的数据结构。在查询特定区间内字典序大于目标字符串的最小子串时,使用线段树进行快速查询和合并,提高了效率。此外,文章还展示了如何实现线段树和后缀自动机的结合,以及处理查询和更新的操作。
摘要由CSDN通过智能技术生成

LINK

如果每个操作只要求我们在整个 S S S串上找到字典序大于 T T T且最小的子串

只需要对 S S S建立 S A M SAM SAM,然后因为所有子串包含在从根节点出发的路径上

我们就一直走和 T T T相等的边,直到匹配到长度 k k k的时候走不下去了

那我们知道需要在第 k k k位大于 T T T,如果不能的话,看一下第 k − 1 k-1 k1位是否能大于…以此类推

但是现在是询问 S S S串的 [ l , r ] [l,r] [l,r]区间,难道我们每次都重新建立 S A M SAM SAM吗?显然不可能。

我们维护每个节点的所有 e n d p o s endpos endpos!!

比如我们现在匹配的长度是 l e n − 1 len-1 len1,限定在 S S S串的 [ l , r ] [l,r] [l,r]区间内找

那么能不能从 u u u节点转移到 v v v节点取决于, v v v是否包含当前匹配的长度 l e n len len的子串

这其实只需要看一下节点 v v v e n d p o s endpos endpos集合在 [ l + l e n − 1 , r ] [l+len-1,r] [l+len1,r]中是否有值即可

因为能转移到节点 v v v说明有这个子串,判断一下位置就好了

至于 e n d p o s endpos endpos,每个节点维护一颗权值线段树,线段树合并预处理就好了

合并过程中需要新开节点,不新开,会互相影响

#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e5+10;
const int N = 200000*20;
const int mod = 1e9+7;
//下面是线段树
int sum[N],ls[N],rs[N],rt[N],num; 
void push_up(int root)
{
	sum[root] = sum[ls[root]]+sum[rs[root]];
}
void add(int &root,int l,int r,int index)
{
	if( l>index || r<index )	return;
	if( !root )	root = ++num;
	if( l==r&&l==index ){ sum[root] = 1; return; }
	int mid = l+r>>1;
	add( ls[root],l,mid,index); add( rs[root],mid+1,r,index);
	push_up(root);
}
int ask(int &root,int l,int r,int L,int R)
{
	if( l>R||r<L )	return 0;
	if( !root )	return 0;
	if( l>=L&&r<=R )	return sum[root];
	int mid = l+r>>1;
	return ask( ls[root],l,mid,L,R )+ask( rs[root],mid+1,r,L,R );
}
int merge(int x,int y,int l,int r)
{
	if( !x || !y )	return x|y;
	if( l==r ){ int p = ++num; sum[p] = sum[x]|sum[y]; return p; }
	int mid = l+r>>1, p = ++num;
	ls[p] = merge( ls[x],ls[y],l,mid);
	rs[p] = merge( rs[x],rs[y],mid+1,r);
	push_up(p);
	return p; 
}
//下面是后缀自动机 
int zi[maxn][27],fa[maxn],len[maxn],id=1,las=1,n;
char a[maxn];
void insert(int c,int index)
{
	int p = las, np = ++id; las = id;
	len[np] = len[p]+1;
	for( ; p&&zi[p][c]==0 ; p=fa[p] )	zi[p][c] = np;
	if( p==0 )	fa[np] = 1;
	else
	{
		int q = zi[p][c];
		if( len[q]==len[p]+1 )	fa[np] = q;
		else
		{
			int nq = ++id; 
			memcpy( zi[nq],zi[q],sizeof zi[nq] );
			fa[nq] = fa[q], len[nq] = len[p]+1;
			fa[np] = fa[q] = nq;
			for( ; p&&zi[p][c]==q ; p = fa[p] )	zi[p][c] = nq;
		}
	}
	add( rt[np],1,n,index );
}
vector<int>vec[maxn];
void dfs(int u)
{
	for( auto v:vec[u] )
	{
		dfs(v);
		rt[u] = merge( rt[u],rt[v],1,n );
		
	}
}
int ans[maxn],now[maxn];

signed main()
{
	scanf("%s",a+1 );	n = strlen( a+1 );
	for(int i=1;i<=n;i++)	insert( a[i]-'a',i );
	for(int i=2;i<=id;i++)	vec[fa[i]].push_back( i );
	dfs(1);//线段树合并 
	int q; cin >> q;
	while( q-- )
	{
		int L,R; scanf("%d%d%s",&L,&R,a+1 ); 
		int le = strlen( a+1 ), p = 1, top = 0;
		now[0] = 1;
		for(int i=1;i<=le;i++)
		{
			int v = zi[p][ a[i]-'a' ];
			if( v&&ask(rt[v],1,n,L+i-1,R) )	
				ans[++top] = a[i]-'a',now[top] = p = zi[p][a[i]-'a'];
			else	break;
		}
		if( top==le )	a[top+1] = 'a'-1;//尝试在下一个位置加上任何一个字母 
		for( ; top>=0 ; top-- )//[1,top]都相等,在top+1适配,说明在top+1位置就需要更大 
		{
			int flag = 0;
			for(int j=a[top+1]-'a'+1;j<=25;j++)//找到比之前大的字母
			{
				int v = zi[now[top]][j];
				if( v&&ask(rt[v],1,n,L+top,R) )
					{ ans[++top] = j; flag = 1; break; }
			}
			if( flag )	break;
		}
		if( top==-1 )	printf("-1\n");
		else
		{
			for(int i=1;i<=top;i++)	cout << char( ans[i]+'a' );
			cout << "\n";
		}
	}
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值