洛谷P4173 残缺的字符串(FFT匹配字符串)

首先考虑完整两串的匹配(我知道可以kmp,但是模糊匹配的思路需要从这里引出
假设短串a长为lena长串b长为lenb
我们知道两串如果完整匹配,他们每一个位置都要相同,即 a [ i ] − b [ y + i ] = 0 a[i]-b[y+i]=0 a[i]b[y+i]=0
所以说a要完整匹配要满足:
∑ i = 0 l e n a a [ i ] − b [ y + i ] = 0 \sum_{i=0}^{lena} a[i]-b[y+i]=0 i=0lenaa[i]b[y+i]=0
这是必要条件,不是充分条件,因为我们发现bd和cc按这种规则也能匹配,为了使偏差的增量恒为正,我们给他平方一下
∑ i = 0 l e n a ( a [ i ] − b [ y + i ] ) 2 = 0 \sum_{i=0}^{lena} (a[i]-b[y+i])^2=0 i=0lena(a[i]b[y+i])2=0
这个时候复杂度显然是n^2的,考虑优化,我们把短串反转记为c,则 a [ i ] = c [ l e n a − i ] a[i]=c[lena-i] a[i]=c[lenai]
∑ i = 0 l e n a ( c [ l e n a − i ] − b [ y + i ] ) 2 = 0 \sum_{i=0}^{lena} (c[lena-i]-b[y+i])^2=0 i=0lena(c[lenai]b[y+i])2=0
∑ i = 0 l e n a ( c [ l e n a − i ] ) 2 + ∑ i = 0 l e n a ( b [ y + i ] ) 2 − 2 ∑ i = 0 l e n a ( c [ l e n a − i ] b [ y + i ] ) = 0 \sum_{i=0}^{lena} (c[lena-i])^2+\sum_{i=0}^{lena}(b[y+i])^2-2\sum_{i=0}^{lena}(c[lena-i]b[y+i])=0 i=0lena(c[lenai])2+i=0lena(b[y+i])22i=0lena(c[lenai]b[y+i])=0
可以发现前两项能线性处理,后一项是卷积,能FFT
那么就结束了完整串匹配
这里也放个没交过的代码,数据范围1e5(如有错误敬请指出

#include
#define mod 950009857
#define gg 7
using namespace std;

char s1[200020],s2[200020];
long long g1[200020],g2[200020];
long long sum2[200020];
long long sum1;
int r[200020];
int lim;

long long kasumi(long long a,long long b)
{
	long long ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

void NTT(long long *a,int kd)
{
	for(int i=0;i<lim;i++) 
	{
		if(i<r[i]) swap(a[i],a[r[i]]);
	}
	for(int mid=1;mid<lim;mid<<=1)
	{
		long long wn=kasumi(gg,(mod-1)/(mid<<1));
		if(kd) wn=kasumi(wn,mod-2);
		for(int i=0;i<lim;i+=(mid<<1))
		{
			long long w=1;
			for(int j=0;j<mid;j++,w=wn*w%mod)
			{
				long long x=a[i+j];
				long long y=a[i+j+mid]*w%mod;
				a[i+j]=(x+y)%mod;
				a[i+j+mid]=(x-y+mod)%mod;
			}
		}
	}
	if(kd)
	{
		long long inv1=kasumi(lim,mod-2);
		for(int i=0;i<lim;i++)
		{
			a[i]=a[i]*inv1%mod;
		}
	}
}



int main()
{
	scanf("%s",s1);
	scanf("%s",s2);
	int lena=strlen(s1);
	int lenb=strlen(s2);
	reverse(s1,s1+lena);
	for(int i=0;i<lena;i++) g1[i]=s1[i]-'a'+1,sum1+=g1[i]*g1[i];
	for(int i=0;i<lenb;i++) g2[i]=s2[i]-'a'+1,sum2[i]=((i>=1)?sum2[i-1]:0)+g2[i]*g2[i];
	int cnt=0;
	for(lim=1;lim<lenb*2;lim<<=1,cnt++);
	for(int i=0;i<lim;i++)
	{
		r[i]=(r[i>>1]>>1)|((i&1)<<(cnt-1));
	}
	NTT(g1,0);NTT(g2,0);
	for(int i=0;i<lim;i++)
	{
		g1[i]=g1[i]*g2[i]%mod;
	}
	NTT(g1,1);
	for(int i=lena-1;i<=lenb;i++)
	{
		if(sum1+sum2[i]-((i>=lena)?sum2[i-lena]:0)-2*g1[i]==0)
		{
			printf("%d\n",i-lena+1);
		}
	}
}

然后开始考虑模糊匹配
如果是*的话要让他能跟所有配上,咋办呢,改下参数,使他能够决定整个匹配函数,怎么改呢
∑ i = 0 l e n a ( a [ i ] − b [ y + i ] ) 2 a [ i ] b [ y + i ] = 0 \sum_{i=0}^{lena} (a[i]-b[y+i])^2a[i]b[y+i]=0 i=0lena(a[i]b[y+i])2a[i]b[y+i]=0
好的,我们继续反转字符串
∑ i = 0 l e n a ( c [ l e n a − i ] − b [ y + i ] ) 2 c [ l e n a − i ] b [ y + i ] = 0 \sum_{i=0}^{lena} (c[lena-i]-b[y+i])^2c[lena-i]b[y+i]=0 i=0lena(c[lenai]b[y+i])2c[lenai]b[y+i]=0
∑ i = 0 l e n a ( c [ l e n a − i ] ) 3 b [ y + i ] + ∑ i = 0 l e n a ( b [ y + i ] ) 3 c [ l e n a − i ] − 2 ∑ i = 0 l e n a ( c [ l e n a − i ] b [ y + i ] ) 2 = 0 \sum_{i=0}^{lena} (c[lena-i])^3b[y+i]+\sum_{i=0}^{lena}(b[y+i])^3c[lena-i]-2\sum_{i=0}^{lena}(c[lena-i]b[y+i])^2=0 i=0lena(c[lenai])3b[y+i]+i=0lena(b[y+i])3c[lenai]2i=0lena(c[lenai]b[y+i])2=0
emmm,这下似乎没法线性搞了,不过没关系也就是多算几个卷积,复杂度同样是nlogn的
代码如下:

#include
#define mod 950009857
#define gg 7
using namespace std;

char s1[300020],s2[300020];
long long g1[1200020],g2[1200020],g3[1200020],g4[1200020],g5[1200020],g6[1200020];
int r[1200020];
int lim;

long long kasumi(long long a,long long b)
{
	long long ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}

void NTT(long long *a,int kd)
{
	for(int i=0;i<lim;i++) 
	{
		if(i<r[i]) swap(a[i],a[r[i]]);
	}
	for(int mid=1;mid<lim;mid<<=1)
	{
		long long wn=kasumi(gg,(mod-1)/(mid<<1));
		if(kd) wn=kasumi(wn,mod-2);
		for(int i=0;i<lim;i+=(mid<<1))
		{
			long long w=1;
			for(int j=0;j<mid;j++,w=wn*w%mod)
			{
				long long x=a[i+j];
				long long y=a[i+j+mid]*w%mod;
				a[i+j]=(x+y)%mod;
				a[i+j+mid]=(x-y+mod)%mod;
			}
		}
	}
	if(kd)
	{
		long long inv1=kasumi(lim,mod-2);
		for(int i=0;i<lim;i++)
		{
			a[i]=a[i]*inv1%mod;
		}
	}
}

int tmp,lena,lenb,cnt=0;

int main()
{
	scanf("%d%d",&lena,&lenb);
	scanf("%s",s1);
	scanf("%s",s2);
	reverse(s1,s1+lena);
	for(int i=0;i<lena;i++)
	{
		if(s1[i]=='*')
		{
			continue;
		}
		tmp=s1[i]-'a'+1;
		g1[i]=tmp*tmp*tmp;
		g3[i]=tmp;
		g5[i]=tmp*tmp;
	}
	for(int i=0;i<lenb;i++)
	{
		if(s2[i]=='*')
		{
			continue;
		}
		tmp=s2[i]-'a'+1;
		g2[i]=tmp;
		g4[i]=tmp*tmp*tmp;
		g6[i]=tmp*tmp;
	}
	for(lim=1;lim<lenb;lim<<=1,cnt++);
	for(int i=0;i<lim;i++)
	{
		r[i]=(r[i>>1]>>1)|((i&1)<<(cnt-1));
	}
	NTT(g1,0);NTT(g2,0);NTT(g3,0);NTT(g4,0);NTT(g5,0);NTT(g6,0);
	for(int i=0;i<lim;i++)
	{
		g1[i]=((g1[i]*g2[i]%mod+g3[i]*g4[i]%mod)%mod-2*g5[i]*g6[i]%mod+mod)%mod;
	}
	NTT(g1,1);
	cnt=0;
	for(int i=lena-1;i<lenb;i++)
	{
		if(g1[i]==0) cnt++;
	}
	printf("%d\n",cnt);
	for(int i=lena-1;i<lenb;i++)
	{
		if(g1[i]==0) printf("%d ",i-lena+2);
	}
}

你可能感兴趣的:(洛谷,FFT/NTT)