【BZOJ4259】残缺的字符串(NTT)

19 篇文章 0 订阅
16 篇文章 0 订阅

题面

很久很久以前,在你刚刚学习字符串匹配的时候,有两个仅包含小写字母的字符串A和B,其中A串长度为m,B串长度为n。可当你现在再次碰到这两个串时,这两个串已经老化了,每个串都有不同程度的残缺。
你想对这两个串重新进行匹配,其中A为模板串,那么现在问题来了,请回答,对于B的每一个位置i,从这个位置开始连续m个字符形成的子串是否可能与A串完全匹配?

Input

第一行包含两个正整数m,n(1<=m<=n<=300000),分别表示A串和B串的长度。
第二行为一个长度为m的字符串A。
第三行为一个长度为n的字符串B。
两个串均仅由小写字母和 ∗ * 号组成,其中 ∗ * 号表示相应位置已经残缺。

Output

第一行包含一个整数k,表示B串中可以完全匹配A串的位置个数。
若k>0,则第二行输出k个正整数,从小到大依次输出每个可以匹配的开头位置(下标从1开始)。

Sample Input

3 7
a*b
aebr*ob

Sample Output

2
1 5

题解

字符串中存在 ∗ * 号,不好哈希,我们可以换种思路:让两个串位置对应相乘,一方存在 ∗ * 号即贡献 0 ,否则只有字符相等才贡献 1 。不难想到这就是翻转后再卷积。

我们设定一个可做NTT的大质数(如998244353),把每一种字母都随机一个不同的较小数字,在串 A A A 中替换为该数字,串 B B B 中替换为该数字的逆元,然后 ∗ * 号替换为 0 。那么我们可以同时求出对应位相乘的乘积和,以及两个串之间同为字母的位置数(所有字母替换为 1 卷一遍)。可匹配的依据就是这两个值相等。

时间复杂度 O ( ( n + m ) log ⁡ ( n + m ) ) O((n+m)\log(n+m)) O((n+m)log(n+m))

CODE

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 300005
#define LL long long
#define ENDL putchar('\n')
#define DB double
#define lowbit(x) (-(x) & (x))
#define FI first
#define SE second
int xchar() {
	static const int maxn = 1000000;
	static char b[maxn];
	static int pos = 0,len = 0;
	if(pos == len) pos = 0,len = fread(b,1,maxn,stdin);
	if(pos == len) return -1;
	return b[pos ++];
}
//#define getchar() xchar()
LL read() {
	LL f = 1,x = 0;int s = getchar();
	while(s < '0' || s > '9') {if(s<0)return -1;if(s=='-')f=-f;s = getchar();}
	while(s >= '0' && s <= '9') {x = (x<<1) + (x<<3) + (s^48);s = getchar();}
	return f*x;
}
void putpos(LL x) {if(!x)return ;putpos(x/10);putchar((x%10)^48);}
void putnum(LL x) {
	if(!x) {putchar('0');return ;}
	if(x<0) putchar('-'),x = -x;
	return putpos(x);
}
void AIput(LL x,int c) {putnum(x);putchar(c);}

const int MOD = 1004535809;
const int pmr = 3;
int n,m,s,o,k;
int fac[MAXN],inv[MAXN],invf[MAXN],pw2[MAXN];
int qkpow(int a,int b) {
	int res = 1;
	while(b > 0) {
		if(b & 1) res = res *1ll* a % MOD;
		a = a *1ll* a % MOD; b >>= 1; 
	}return res;
}
int rev[MAXN<<2],xm[MAXN<<2],om;
void NTT(int *s,int n,int op) {
	for(int i = 1;i < n;i ++) {
		rev[i] = (rev[i>>1]>>1) | ((i&1) ? (n>>1):0);
		if(rev[i] < i) swap(s[rev[i]],s[i]);
	}
	om = qkpow(pmr,(MOD-1)/n); xm[0] = 1;
	if(op < 0) om = qkpow(om,MOD-2);
	for(int i = 1;i <= n;i ++) xm[i] = xm[i-1]*1ll*om%MOD;
	for(int k = 2,t = n>>1;k <= n;k <<= 1,t >>= 1) {
		for(int j = 0;j < n;j += k) {
			for(int i = j,l = 0;i < j+(k>>1);i ++,l += t) {
				int A = s[i],B = s[i+(k>>1)];
				s[i] = (0ll+A + xm[l]*1ll*B) % MOD;
				s[i+(k>>1)] = ((0ll+A-xm[l]*1ll*B) % MOD+MOD)%MOD;
			}
		}
	}
	if(op < 0) {
		int invn = qkpow(n,MOD-2);
		for(int i = 0;i < n;i ++) s[i] = s[i] *1ll* invn % MOD;
	}return ;
}
int A[MAXN<<2],B[MAXN<<2],A2[MAXN<<2],B2[MAXN<<2];
int nb[26];
char a[MAXN],b[MAXN];
int main() {
	fac[0]=fac[1]=inv[0]=inv[1]=invf[0]=invf[1]=1;
	for(int i = 2;i <= MAXN-5;i ++) {
		fac[i] = fac[i-1] *1ll* i % MOD;
		inv[i] = (MOD-inv[MOD%i]) *1ll* (MOD/i) % MOD;
		invf[i] = invf[i-1] *1ll* inv[i] % MOD;
	}
	int rd = 81097;
	for(int i = 0;i < 26;i ++) nb[i] = (rd = rd*233ll%(MAXN-5));
	n = read();m = read();
	scanf("%s",a + 1); scanf("%s",b + 1);
	for(int i = 1;i <= n;i ++) {
		if(a[i] != '*') {
			A[i] = 1;
			A2[i] = nb[a[i]-'a'];
		}
	}
	for(int j = 1;j <= m;j ++) {
		if(b[m-j+1] != '*') {
			B[j] = 1;
			B2[j] = inv[nb[b[m-j+1]-'a']];
		}
	}
	int le = 1;while(le <= n+m) le <<= 1;
	NTT(A,le,1); NTT(B,le,1); NTT(A2,le,1); NTT(B2,le,1);
	for(int i = 0;i < le;i ++) A[i] = A[i] *1ll* B[i] % MOD, A2[i] = A2[i] *1ll* B2[i] % MOD;
	NTT(A,le,-1); NTT(A2,le,-1);
	queue<int> qu;
	for(int i = 1;i <= m-n+1;i ++) {
		int ad = m-i+2;
		if(A2[ad] == A[ad]) qu.push(i);
	}
	AIput(qu.size(),'\n');
	while(!qu.empty()) {
		int t = qu.front();qu.pop();
		AIput(t,qu.empty() ? '\n':' ');
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值