P4173 残缺的字符串(NTT字符串匹配模板)

传送门

考虑如何用多项式乘法匹配字符串

s s s串长 n n n, t t t串长 m m m,下标从零开始

把字母转化为 [ 1 , 26 ] [1,26] [1,26]的数字,数组分别为 f , g f,g f,g

定义 a n s [ x ] = 0 ans[x]=0 ans[x]=0表示 s s s串的 [ x , x + m − 1 ] [x,x+m-1] [x,x+m1] t t t串匹配

那么如果 a n s [ x ] = ∑ i = 0 m − 1 ( f [ x + i ] − g [ i ] ) = 0 ans[x]=\sum\limits_{i=0}^{m-1}(f[x+i]-g[i])=0 ans[x]=i=0m1(f[x+i]g[i])=0说明是匹配的

等等!!不太对!!

因为这样 a b ab ab b a ba ba也是相互匹配的,因为正负抵消变成了零

为了消除正负号的影响,我们平方一下

a n s [ x ] = ∑ i = 0 m − 1 ( f [ x + i ] − g [ i ] ) 2 ans[x]=\sum\limits_{i=0}^{m-1}(f[x+i]-g[i])^2 ans[x]=i=0m1(f[x+i]g[i])2

= ∑ i = 0 m − 1 f [ x + i ] 2 + ∑ i = 0 m − 1 g [ i ] 2 − 2 ∑ i = 0 m − 1 f [ x + i ] ∗ g [ i ] =\sum\limits_{i=0}^{m-1}f[x+i]^2+\sum\limits_{i=0}^{m-1}g[i]^2-2\sum\limits_{i=0}^{m-1}f[x+i]*g[i] =i=0m1f[x+i]2+i=0m1g[i]22i=0m1f[x+i]g[i]

第一项可以前缀和预处理,第二项是常数直接暴力,第三项似乎是一个卷积的形式

我们把 g g g反转记作 G [ i ] = g [ m − 1 − i ] G[i]=g[m-1-i] G[i]=g[m1i]

那么 ∑ i = 0 m − 1 f [ x + i ] ∗ G [ m − 1 − i ] \sum\limits_{i=0}^{m-1}f[x+i]*G[m-1-i] i=0m1f[x+i]G[m1i]

发现 ( x + i ) + ( m − 1 − i ) = m − 1 + x (x+i)+(m-1-i)=m-1+x (x+i)+(m1i)=m1+x是定值

e = m + x − 1 e=m+x-1 e=m+x1

所以可以改写成 ∑ i = 0 e f [ i ] ∗ G [ e − i ] \sum\limits_{i=0}^{e}f[i]*G[e-i] i=0ef[i]G[ei]

所以可以得出

a n s [ x ] = ∑ i = 0 m − 1 g [ i ] 2 + ∑ i = 0 m − 1 f [ x + i ] 2 + A [ x + m − 1 ] ans[x]=\sum\limits_{i=0}^{m-1}g[i]^2+\sum\limits_{i=0}^{m-1}f[x+i]^2+A[x+m-1] ans[x]=i=0m1g[i]2+i=0m1f[x+i]2+A[x+m1]

其中 A [ i ] A[i] A[i]是卷积的第 i i i


回到这题,因为有通配符的存在,所以我们需要给匹配规则改一下

也就是令通配符位置为 0 0 0

a n s [ x ] = ∑ i = 0 m − 1 ( f [ x + i ] − g [ i ] ) 2 ∗ f [ x + i ] ∗ g [ i ] ans[x]=\sum\limits_{i=0}^{m-1}(f[x+i]-g[i])^2*f[x+i]*g[i] ans[x]=i=0m1(f[x+i]g[i])2f[x+i]g[i]

展开可以得到

a n s [ x ] = ∑ i = 0 m − 1 f [ x + i ] 3 ∗ g [ i ] + ∑ i = 0 m − 1 g [ i ] 3 ∗ f [ x + i ] − 2 ∑ i = 0 m − 1 g i 2 f [ x + i ] 2 ans[x]=\sum\limits_{i=0}^{m-1}f[x+i]^3*g[i]+\sum\limits_{i=0}^{m-1}g[i]^3*f[x+i]-2\sum\limits_{i=0}^{m-1}g_i^2f[x+i]^2 ans[x]=i=0m1f[x+i]3g[i]+i=0m1g[i]3f[x+i]2i=0m1gi2f[x+i]2

我们还是令 G [ i ] = g [ m − 1 − i ] G[i]=g[m-1-i] G[i]=g[m1i]

a n s [ x ] = ∑ i = 0 m − 1 f [ x + i ] 3 ∗ G [ m − 1 − i ] + ∑ i = 0 m − 1 G [ m − 1 − i ] 3 ∗ f [ x + i ] − 2 ∑ i = 0 m − 1 G [ m − 1 − i ] 2 f [ x + i ] 2 ans[x]=\sum\limits_{i=0}^{m-1}f[x+i]^3*G[m-1-i]+\sum\limits_{i=0}^{m-1}G[m-1 -i]^3*f[x+i]-2\sum\limits_{i=0}^{m-1}G[m-1-i]^2f[x+i]^2 ans[x]=i=0m1f[x+i]3G[m1i]+i=0m1G[m1i]3f[x+i]2i=0m1G[m1i]2f[x+i]2

我们令 e = m − 1 + x e=m-1+x e=m1+x

a n s [ x ] = ∑ i = 0 e f [ i ] 3 ∗ G [ e − i ] + ∑ i = 0 e G [ e − i ] 3 ∗ f [ i ] − 2 ∑ i = 0 e G [ e − i ] 2 f [ i ] 2 ans[x]=\sum\limits_{i=0}^{e}f[i]^3*G[e-i]+\sum\limits_{i=0}^{e}G[e-i]^3*f[i]-2\sum\limits_{i=0}^{e}G[e-i]^2f[i]^2 ans[x]=i=0ef[i]3G[ei]+i=0eG[ei]3f[i]2i=0eG[ei]2f[i]2

所以 a n s [ x ] ans[x] ans[x]就是所有卷积的第 m − 1 + x m-1+x m1+x

于是可以写出下面的代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 9e6+10;
const int mod =  998244353,G=3,GI = 332748118;
int r[maxn],f[maxn],g[maxn],ans[maxn],n,m;
char s[maxn],t[maxn];
int quick(int x,int n)
{
	int ans = 1;
	for( ; n ; n>>=1,x=1ll*x*x%mod )
		if( n&1 )	ans = 1ll*ans*x%mod;
	return ans;
}
void NTT(int *a,int limit,int type)
{
	for(int i=0;i<limit;i++)	if( i<r[i] )	swap(a[i],a[r[i]]);
	for(int mid = 1;mid<limit;mid<<=1)
	{
		int wn = quick( (type==1)?G:GI,(mod-1)/(mid<<1));
		for(int R=mid<<1,i=0;i<limit;i+=R)
		for(int k=0,w=1;k<mid;k++,w=1ll*w*wn%mod)
		{
			int x = a[i+k], y = 1ll*w*a[i+k+mid]%mod;
			a[i+k] = (1ll*x+y)%mod, a[i+k+mid] = (1ll*x-y+mod)%mod;
		}
	}
	if( type==1 )	return;
	int inv = quick(limit,mod-2);
	for(int i=0;i<limit;i++)	a[i] = 1ll*a[i]*inv%mod;
}
void mul(int *a,int *b,int n,int m)
{
	int limit = 1;
	while( limit<=n+m )	limit<<=1;
	for(int i=0;i<limit;i++)	r[i] = ( r[i>>1]>>1 ) | ( (i&1)?limit>>1:0 );
	for(int i=0;i<limit;i++)
	{
		if( i>=n )	a[i] = 0;
		if( i>=m )	b[i] = 0;
	}
	NTT(a,limit,1); NTT(b,limit,1);
	for(int i=0;i<limit;i++)	a[i] = 1ll*a[i]*b[i]%mod;
	NTT(a,limit,-1);
}
vector<int>vec;
signed main()
{
	scanf("%d%d",&m,&n);
	scanf("%s%s",t,s);
	reverse(t,t+m );
	int limit = 1;
	while( limit<=n+m )	limit<<=1;
	for(int i=0;i<n;i++)	f[i] = s[i]=='*'?0:pow(s[i]-'0',3);
	for(int i=0;i<m;i++)	g[i] = t[i]=='*'?0:pow(t[i]-'0',1);
	mul(f,g,n,m);
	for(int i=0;i<limit;i++)	ans[i] = ( 1ll*ans[i]+f[i+m-1] )%mod;
	for(int i=0;i<n;i++)	f[i] = s[i]=='*'?0:pow(s[i]-'0',1);
	for(int i=0;i<m;i++)	g[i] = t[i]=='*'?0:pow(t[i]-'0',3);
	mul(f,g,n,m);
	for(int i=0;i<limit;i++)	ans[i] = ( 1ll*ans[i]+f[i+m-1] )%mod;
	for(int i=0;i<n;i++)	f[i] = s[i]=='*'?0:pow(s[i]-'0',2);
	for(int i=0;i<m;i++)	g[i] = t[i]=='*'?0:pow(t[i]-'0',2);
	mul(f,g,n,m);		
	for(int i=0;i<limit;i++)	ans[i] = ( 1ll*ans[i]-2*f[i+m-1] )%mod;
	for(int i=0;i<=n-m;i++)
		if( ans[i]==0 )	vec.push_back(i+1);
	printf("%d\n",vec.size() );
	for(int i=0;i<vec.size();i++)
		printf("%d ",vec[i] );
}

但是 T T T了…

无奈,只能一直把正变换累加到 a n s ans ans去,然后一次性做掉逆变换, A C AC AC

#include <bits/stdc++.h>
using namespace std;
const int maxn = 6e6+10;
const int mod =  998244353,G=3,GI = 332748118;
int r[maxn],f[maxn],g[maxn],ans[maxn],n,m;
char s[maxn],t[maxn];
int quick(int x,int n)
{
	int ans = 1;
	for( ; n ; n>>=1,x=1ll*x*x%mod )
		if( n&1 )	ans = 1ll*ans*x%mod;
	return ans;
}
void NTT(int *a,int limit,int type)
{
	for(int i=0;i<limit;i++)	if( i<r[i] )	swap(a[i],a[r[i]]);
	for(int mid = 1;mid<limit;mid<<=1)
	{
		int wn = quick( (type==1)?G:GI,(mod-1)/(mid<<1));
		for(int R=mid<<1,i=0;i<limit;i+=R)
		for(int k=0,w=1;k<mid;k++,w=1ll*w*wn%mod)
		{
			int x = a[i+k], y = 1ll*w*a[i+k+mid]%mod;
			a[i+k] = (1ll*x+y)%mod, a[i+k+mid] = (1ll*x-y+mod)%mod;
		}
	}
	if( type==1 )	return;
	int inv = quick(limit,mod-2);
	for(int i=0;i<limit;i++)	a[i] = 1ll*a[i]*inv%mod;
}
void mul(int *a,int *b,int n,int m)
{
	int limit = 1;
	while( limit<=n+m )	limit<<=1;
	for(int i=0;i<limit;i++)	r[i] = ( r[i>>1]>>1 ) | ( (i&1)?limit>>1:0 );
	for(int i=0;i<limit;i++)
	{
		if( i>=n )	a[i] = 0;
		if( i>=m )	b[i] = 0;
	}
	NTT(a,limit,1); NTT(b,limit,1);
	for(int i=0;i<limit;i++)	a[i] = 1ll*a[i]*b[i]%mod;
}
vector<int>vec;
signed main()
{
	scanf("%d%d",&m,&n);
	scanf("%s%s",t,s);
	reverse(t,t+m );
	int limit = 1;
	while( limit<=n+m )	limit<<=1;
	for(int i=0;i<n;i++)	f[i] = s[i]=='*'?0:pow(s[i]-'0',3);
	for(int i=0;i<m;i++)	g[i] = t[i]=='*'?0:pow(t[i]-'0',1);
	mul(f,g,n,m);
	for(int i=0;i<limit;i++)	ans[i] = ( 1ll*ans[i]+f[i] )%mod;
	for(int i=0;i<n;i++)	f[i] = s[i]=='*'?0:pow(s[i]-'0',1);
	for(int i=0;i<m;i++)	g[i] = t[i]=='*'?0:pow(t[i]-'0',3);
	mul(f,g,n,m);
	for(int i=0;i<limit;i++)	ans[i] = ( 1ll*ans[i]+f[i] )%mod;
	for(int i=0;i<n;i++)	f[i] = s[i]=='*'?0:pow(s[i]-'0',2);
	for(int i=0;i<m;i++)	g[i] = t[i]=='*'?0:pow(t[i]-'0',2);
	mul(f,g,n,m);		
	for(int i=0;i<limit;i++)	ans[i] = ( 1ll*ans[i]-2*f[i] )%mod;
	NTT(ans,limit,-1);
	for(int i=0;i<=n-m;i++)
		if( ans[i+m-1]==0 )	vec.push_back(i+1);
	printf("%d\n",vec.size() );
	for(int i=0;i<vec.size();i++)
		printf("%d ",vec[i] );
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值