P4022 [CTSC2012]熟悉的文章(广义SAM+决策单调性)

LINK

显然 L 0 L_0 L0有单调性,从而转化为判定性问题

由于需要超过原串长度的 90 % 90\% 90%,不妨定义 f [ i ] f[i] f[i]表示前 i i i个字符形成的最大熟悉字符

使用后缀自动机可以预处理一个 s u f i suf_i sufi表示最长可以使 [ i − s u f i + 1 , i ] [i-suf_i+1,i] [isufi+1,i]能匹配上原串

先继承上一次的状态 f [ i ] = f [ i − 1 ] f[i]=f[i-1] f[i]=f[i1]

s u f i > = L 0 suf_i>=L_0 sufi>=L0,说明这个后缀可以形成熟悉子串

l = i − s u f i , r = i − L 0 l=i-suf_i,r=i-L_0 l=isufi,r=iL0

那么有 f [ i ] = max ⁡ j = l r { f [ j ] + i − j } f[i]=\max_{j=l}^{r}\{f[j]+i-j\} f[i]=maxj=lr{f[j]+ij}

这部分维护一个 f [ j ] − j f[j]-j f[j]j的线段树区间 m a x max max就可以解决

时间复杂度 O ( n l o g 2 ) O(nlog^2) O(nlog2)

90分代码LINK

复杂度还不够优秀,进一步观察到每次只需要从 [ i − s u f i , i − L 0 ] [i-suf_i,i-L_0] [isufi,iL0]转移而来

发现具有决策单调性,因为 i − s u f i i-suf_i isufi单调递增,于是可以套一个单调队列求区间最值

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2.4e6+10;
const int inf = 1e9;
int n,m,len;
char a[maxn];
int zi[maxn][2],l[maxn],fa[maxn],id = 1,las = 1;
void insert(int c)
{
	int p = las, np = ++id; las = id;
	l[np] = l[p]+1;
	for( ; p && zi[p][c]==0  ;p=fa[p] )	zi[p][c] = np;
	if( !p )	fa[np] = 1;
	else
	{
		int q = zi[p][c];
		if( l[q]==l[p]+1 )	fa[np] = q;
		else
		{
			int nq = ++id;
			fa[nq] = fa[q], l[nq] = l[p]+1;
			memcpy( zi[nq],zi[q], sizeof zi[q] );
			fa[np] = fa[q] = nq;
			for( ; p&&zi[p][c]==q; p=fa[p] )	zi[p][c] = nq;
		}
	}
}
int suf[maxn],f[maxn],L;
int tail,head,q[maxn];
int isok(int L0)
{
	head = 1, tail = 0;	int z = 0;
	for(int i=1;i<=len;i++)
	{
		f[i] = f[i-1];
		if( suf[i]<L0 )	continue;
		while( z<=i-L0 )
		{
			while( tail>=head && f[q[tail]]-q[tail]<=f[z]-z )	tail--;
			q[++tail] = z; z++;
		}
		while( q[head]<i-suf[i] )	head++;
		f[i] = max( f[i], f[q[head]]-q[head]+i );
		if( f[i]*10>=len*9 )	return f[i];
	}
	return f[len];
}
int main()
{
	scanf("%d%d",&n,&m );
	for(int i=1;i<=m;i++)
	{
		scanf("%s",a+1 );
		int le = strlen( a+1 );
		for(int j=1;j<=le;j++)	insert( a[j]-'0' );
		las = 1;
	}
	while( n-- )
	{
		scanf("%s",a+1 );	len = strlen( a+1 );
		int L = 0, p = 1;
		for(int i=1;i<=len;i++)
		{
			int c = a[i]-'0';
			if( zi[p][c] )	p = zi[p][c], L++;
			else
			{
				while( p&&zi[p][c]==0 )	p = fa[p];
				if( !p )	p = 1, L = 0;
				else	L = l[p]+1, p = zi[p][c];
			}
			suf[i] = L;
		}
		int l = 1, r = len, ans = 0;
		while( r>=l )
		{
			int mid = l+r>>1;
			if( isok(mid)*10>=len*9 )	l = mid+1, ans = mid;
			else	r = mid-1;
		}
		printf("%d\n",ans );
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值