“21 天好习惯”第一期-1

CF528D Fuzzy Search

传送门

k = 0 k=0 k=0

题目保证只有四个字母 " A T G C " "ATGC" "ATGC",暗示我们可以先分成4种情况处理,最后加起来。

比如说文本串 S = " T A A T G C A " S="TAATGCA" S="TAATGCA"和模板串 T = " A A T G C " T="AATGC" T="AATGC"

先处理 A A A的情况:

S = " # A A # # # A " S="\#AA\#\#\#A" S="#AA###A"

T = " A A # # # " T="AA\#\#\#" T="AA###"

S ( 5 ) S(5) S(5)(下标以0开始)结尾的长度为5的子串和模板串匹配的长度为2

同理,处理 T T T的情况,以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串匹配的长度为1

处理 C C C的情况,以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串匹配的长度为1

处理 G G G的情况,以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串匹配的长度为1

加起来后可知,以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串匹配的长度为5,即以 S ( 5 ) S(5) S(5)结尾的长度为5的子串和模板串完全匹配

k > 0 k>0 k>0时,可以理解为将文本串中的字母 C C C(当前在处理字母 c c c的情况)往两边扩散 k k k个距离。

比如 k = 1 k=1 k=1,此时先处理 A A A的情况:

S = " A A A A # # A " S="AAAA\#\#A" S="AAAA##A"

T = " A A # # # " T="AA\#\#\#" T="AA###"

S ( 5 ) S(5) S(5)(下标以0开始)结尾的长度为5的子串和模板串匹配的长度为2

当前处理字符 c c c时,接下来就是构造多项式 f = ∑ i = 0 n − 1 [ S [ i ] = = ′ c ′ ] ∗ x i f=\sum_{i=0}^{n-1}[S[i]=='c']*x^i f=i=0n1[S[i]==c]xi,多项式 g = ∑ i = 0 m − 1 [ T [ m − 1 − i ] = = ′ c ′ ] ∗ x i g=\sum_{i=0}^{m-1}[T[m-1-i]=='c']*x^i g=i=0m1[T[m1i]==c]xi,然后 a = f ∗ s a=f*s a=fs a ( d ) = ∑ i + j = d [ S [ i ] = = ′ c ′ & & T [ m − 1 − j ] = = ′ c ′ ] x d a(d)=\sum_{i+j=d}{[S[i]=='c'\&\&T[m-1-j]=='c']x^d} a(d)=i+j=d[S[i]==c&&T[m1j]==c]xd就表示以 S ( i ) S(i) S(i)结尾的长度为 m − 1 m-1 m1的子串和模板串匹配的长度。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=(1<<19)+7,mod=998244353;
inline int qpow(int x,int y) {
	int res(1);
	while(y) {
		if(y&1) res=1LL*res*x%mod;
		x=1LL*x*x%mod;
		y>>=1;
	}
	return res;
}
int r[maxn];
inline void ntt(int *x,int lim,int opt) {
	int i,j,k,m,gn,g,tmp;
	for(i=0; i<lim; ++i)
		if(i<r[i]) swap(x[i],x[r[i]]);
	for(m=2; m<=lim; m<<=1) {
		k=m>>1;
		gn=qpow(3,(mod-1)/m);
		for(i=0; i<lim; i+=m) {
			g=1;
			for(j=0; j<k; ++j,g=1LL*g*gn%mod) {
				tmp=1LL*x[i+j+k]*g%mod;
				x[i+j+k]=(x[i+j]-tmp+mod)%mod;
				x[i+j]=(x[i+j]+tmp)%mod;
			}
		}
	}
	if(opt==-1) {
		reverse(x+1,x+lim);
		int inv=qpow(lim,mod-2);
		for(i=0; i<lim; ++i) x[i]=1LL*x[i]*inv%mod;
	}
}
int A[maxn],B[maxn],cnt[maxn],n,m,k,lim(1);
char a[maxn],b[maxn];
void solve(char c) {
	for(int i=0;i<lim;++i) A[i]=B[i]=0;
	for(int i=0,last=-1e9;i<n;++i) {
		if(a[i]==c) last=i;
		if(i-last<=k) A[i]=1;
	}
	for(int i=n-1,last=1e9;~i;--i) {
		if(a[i]==c) last=i;
		if(last-i<=k) A[i]=1;
	}
	for(int i=0;i<m;++i) B[i]=(b[m-1-i]==c);
	ntt(A,lim,1),ntt(B,lim,1);
	for(int i=0; i<lim; ++i) A[i]=1LL*A[i]*B[i]%mod;
	ntt(A,lim,-1);
	for(int i=0;i<lim;++i) cnt[i]+=A[i];
}
int main() {
	scanf("%d%d%d",&n,&m,&k);
	scanf("%s%s",a,b);
	while(lim<(n<<1)) lim<<=1;
	for(int i=0; i<lim; ++i) r[i]=(i&1)*(lim>>1)+(r[i>>1] >> 1);
	for(int i=0;i<4;++i) solve("ATGC"[i]);
	int ans(0);
	for(int i=m-1;i<lim;++i) ans+=(cnt[i]==m);
	cout<<ans<<'\n';
	return 0;
}

在这里插入图片描述

P4173 残缺的字符串

传送门

文本串为 A ( 长 度 为 n ) A(长度为n) A(n),模式串为 B ( 长 度 为 m ) B(长度为m) B(m)

不考虑通配符的时候,可以用完全匹配函数可以是 P ( x ) = ∑ i = 0 m − 1 [ B ( i ) − A ( x − m + 1 + i )   ] 2 {P(x)=\sum_{i=0}^{m-1}{[B(i)-A(x-m+1+i)\ ]^2}} P(x)=i=0m1[B(i)A(xm+1+i) ]2

将通配符赋值为 0 0 0,那么有完全匹配函数 P ( x ) = ∑ i = 0 m − 1 [ B ( i ) − A ( x − m + 1 + i )   ] 2 ∗ A ( x − m + 1 + i ) ∗ B ( i ) {P(x)=\sum_{i=0}^{m-1}{[B(i)-A(x-m+1+i)\ ]^2*A(x-m+1+i)*B(i)}} P(x)=i=0m1[B(i)A(xm+1+i) ]2A(xm+1+i)B(i)

老套路: S ( i ) = B ( m − 1 − i ) , B ( i ) = S ( m − 1 − i ) S(i)=B(m-1-i),B(i)=S(m-1-i) S(i)=B(m1i),B(i)=S(m1i)

那么有完全匹配函数 P ( x ) = ∑ i = 0 m − 1 S ( m − 1 − i ) 3 ∗ A ( x − m + 1 + i ) + ∑ i = 0 m − 1 A ( x − m + 1 + i ) 3 ∗ S ( m − 1 − i ) − 2 ∗ ∑ i = 0 m − 1 A ( x − m + 1 + i ) 2 ∗ S ( m − 1 − i ) 2 {P(x)=\sum_{i=0}^{m-1}{S(m-1-i)^3*A(x-m+1+i)}+\sum_{i=0}^{m-1}{A(x-m+1+i)^3*S(m-1-i)}-2*\sum_{i=0}^{m-1}{A(x-m+1+i)^2*S(m-1-i)^2}} P(x)=i=0m1S(m1i)3A(xm+1+i)+i=0m1A(xm+1+i)3S(m1i)2i=0m1A(xm+1+i)2S(m1i)2

P ( x ) = ∑ i + j = x S ( i ) 3 ∗ A ( j ) + ∑ i + j = x A ( i ) 3 ∗ S ( j ) − 2 ∗ ∑ i + j = x A ( i ) 2 ∗ S ( j ) 2 {P(x)=\sum_{i+j=x}{S(i)^3*A(j)}+\sum_{i+j=x}{A(i)^3*S(j)}-2*\sum_{i+j=x}{A(i)^2*S(j)^2}} P(x)=i+j=xS(i)3A(j)+i+j=xA(i)3S(j)2i+j=xA(i)2S(j)2

做7次 F F T FFT FFT就好了,浮点数精度可能会有问题, p ( x ) < 1 p(x)<1 p(x)<1就可以看作 0 0 0了,我看有的人是 p ( x ) < 1 e − 7 p(x)<1e-7 p(x)<1e7,而我改成 p ( x ) < 1 e − 6 p(x)<1e-6 p(x)<1e6都wa。

N T T NTT NTT涉及取余( p ( x ) p(x) p(x)会大于 m o d ( 998244353 ) mod(998244353) mod(998244353)),可能会影响结果?我试了一下只有65分。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=(1<<20)+7;
const double PI=acos(-1.0);
struct Complex{
	double x,y;
	Complex(double _x=0.0,double _y=0.0) {
		x=_x;
		y=_y;
	}
	Complex operator-(const Complex &b) const {
		return Complex(x-b.x,y-b.y);
	}
	Complex operator+(const Complex &b) const {
		return Complex(x+b.x,y+b.y);
	}
	Complex operator*(const Complex &b) const {
		return Complex(x*b.x-y*b.y,x*b.y+y*b.x);
	}
};
int r[maxn];
inline void fft(Complex *y,int len,int opt) {
	for(int i=0;i<len;++i)
		if(i<r[i]) swap(y[i],y[r[i]]);
	for(int h=2;h<=len;h<<=1) {
		Complex wn(cos(2*PI/h),sin(opt*2*PI/h));
		for(int j=0;j<len;j+=h) {
			Complex w(1,0);
			for(int k=j;k<j+h/2;++k) {
				Complex tmp=w*y[k+h/2];
				y[k+h/2]=y[k]-tmp;
				y[k]=y[k]+tmp;
				w=w*wn;
			}
		}
	}
	if(opt==-1) {
		for(int i=0;i<len;++i) y[i].x/=len;
	}
}
Complex A[maxn],B[maxn],p[maxn];
double x[maxn],y[maxn];
char a[maxn/2],b[maxn/2];
int main() {
	int n,m,k,lim(1);
	scanf("%d%d",&m,&n);
	scanf("%s%s",b,a);
	while(lim<(n<<1)) lim<<=1;
	for(int i=0; i<lim; ++i) r[i]=(i&1)*(lim>>1)+(r[i>>1] >> 1);
	for(int i=0;i<n;++i) x[i]=(a[i]!='*')?a[i]-'a'+1:0,A[i]=Complex(x[i]*x[i]*x[i],0);
	for(int i=0;i<m;++i) y[i]=(b[m-1-i]!='*')?b[m-1-i]-'a'+1:0,B[i]=Complex(y[i],0);
	fft(A,lim,1),fft(B,lim,1);
	for(int i=0;i<lim;++i) {
		p[i]=p[i]+A[i]*B[i];
		A[i]=Complex(x[i],0);
		B[i]=Complex(y[i]*y[i]*y[i],0);
	}
	fft(A,lim,1),fft(B,lim,1);
	for(int i=0;i<lim;++i) {
		p[i]=p[i]+A[i]*B[i];
		A[i]=Complex(x[i]*x[i],0);
		B[i]=Complex(y[i]*y[i],0);
	}
	fft(A,lim,1),fft(B,lim,1);
	for(int i=0;i<lim;++i) p[i]=p[i]-A[i]*B[i]*Complex(2,0);
	fft(p,lim,-1);
	vector<int>ans;
	for(int i=m-1;i<n;++i) {
		if(fabs(p[i].x)<1) ans.emplace_back(i-m+2);
	}
	cout<<ans.size()<<'\n';
	for(int i:ans) cout<<i<<' ';
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值