特殊字符串匹配

有时,会遇到特殊字符串匹配。

第一种

通配符。

这时通常要用NTT。

记两个字符串的差距为\(A_i\times B_i\times (A_i-B_i)^2\)

若每个位置的差距之和为0,则字符串相等。

若是通配符,把\(A_i/B_i\)变为0即可。

把式子拆开后,反转其中一个串,用NTT优化计算。

第二种

可以每个位置同时加同一个数。

差分即可。

第三种

最小表示法相同

注:这里的最小表示指将该串最靠前出现的字符都替换为字符集的第一个字符,次靠前出现的字符都替换为字符集的第二个字符,以此类推。

例如,字符集是小写字母时,reverse 的最小表示是 abcbadb,test 和 calc 本质相同。

记录每个字符的前驱距离它的位置,没有则为\(-1\)

判断是否相等时,对于每种字符,应把它在这两个串中分别出现的第一个位置修改为\(-1\)


例题:

有一个长度为 n 的仅含前 m 个小写字母的串 S。你希望知道这个串本质不同的子串有多少个。
定义两个串本质不同当且仅当它们的最小表示不同。

类似后缀数组,把所有后缀的最小表示按字典序排序,并求出相邻的LCP。最后用\(\frac{n\times (n+1)}{2}\)减去LCP长度之和即可。

求LCP可以使用二分。判断子串是否相等时用上面的第三种方法。

使用哈希维护。

后缀排序时求LCP即可。

代码:

#include <stdio.h>
#include <stdlib.h>
#define se 13131
#define inf 99999999
#define ll unsigned long long
char zf[50010];int sz[50010],m,n;
int dy[10][50010],la[10];ll mi[50010],ha[50010];
ll getha(int l,int r)
{
	if(l==0)
		return ha[r];
	return ha[r]-ha[l-1]*mi[r-l+1];
}
bool same(int l,int r,int a,int b)
{
	ll h1=getha(l,r),h2=getha(a,b);
	for(int x=0;x<m;x++)
	{
		int z=dy[x][l];
		if(z<=r)
			h1-=sz[z]*mi[r-z];
		z=dy[x][a];
		if(z<=b)
			h2-=sz[z]*mi[b-z];
	}
	return h1==h2;
}
int getlcp(int a,int b)
{
	int l=0,r=a;
	if(b>r)r=b;
	r=n-r;
	while(l<r)
	{
		int z=(l+r+1)>>1;
		if(same(a,a+z-1,b,b+z-1))
			l=z;
		else
			r=z-1;
	}
	return l;
}
int cmp(const void*a2,const void*b2)
{
	int a=*(int*)a2,b=*(int*)b2,c=getlcp(a,b);
	int sa=0,sb=0;
	if(a+c<n)
	{
		int t=dy[zf[a+c]-'a'][a];
		for(int i=0;i<m;i++)
		{
			if(dy[i][a]<=t)
				sa+=1;
		}
	}
	if(b+c<n)
	{
		int t=dy[zf[b+c]-'a'][b];
		for(int i=0;i<m;i++)
		{
			if(dy[i][b]<=t)
				sb+=1;
		}
	}
	return sa-sb;
}
int px[50010],he[50010];
int main()
{
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	scanf("%d%d%s",&n,&m,zf);
	for(int i=0;i<m;i++)la[i]=inf;
	for(int i=n-1;i>=0;i--)
	{
		la[zf[i]-'a']=i;
		for(int j=0;j<m;j++)
			dy[j][i]=la[j];
	}
	for(int i=0;i<m;i++)la[i]=-1;
	for(int i=0;i<n;i++)
	{
		int s=zf[i]-'a';
		if(la[s]==-1)
			sz[i]=0;
		else
			sz[i]=i-la[s];
		la[s]=i;px[i]=i;
	}
	mi[0]=1;
	for(int i=1;i<=n;i++)
		mi[i]=mi[i-1]*se;
	ll h=0;
	for(int i=0;i<n;i++)
	{
		h=h*se+sz[i];
		ha[i]=h;
	}
	qsort(px,n,sizeof(int),cmp);
	for(int i=1;i<n;i++)
		he[i]=getlcp(px[i-1],px[i]);
	int ans=1ll*n*(n+1)/2;
	for(int i=1;i<n;i++)
		ans-=he[i];
	printf("%d",ans);
	return 0;
}
posted @ 2020-10-06 16:53  lnzwz  阅读(313)  评论(0编辑  收藏  举报