NOI模拟 字符矩阵

本文介绍了一道名为CF613E的算法题目解决方案,通过动态规划的方法解决字符串匹配问题,包括双哈希技巧及复杂度分析。

原题cf613e
官方题解

神级 D P DP DP。把一条匹配路径分为左中右三段,左边和右边都是往一个方向走一段然后返回,而中间就是往一个方向蛇皮走位。
于是串分两个方向两次 D P DP DP
注意 D P DP DP的细节判断,以及哈希区间的左右方向。对于 m m m较小的情况可以直接算。记得要双哈希,我太难了。

#include<bits/stdc++.h>
using namespace std;
#define puu pair<int,int>
#define mp make_pair
const int maxn=2e3+10,mod=1e9+7;
const int base1=37,base2=131;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void Add(int &x,int y){x=(x+y)%mod;}
int p1[maxn],p2[maxn];int n,m,ans=0,f[2][maxn][maxn];
struct str{
	int pre1[maxn],suf1[maxn];
	int pre2[maxn],suf2[maxn];
	char s[maxn];int len;
	inline void read(){
		scanf("%s",s+1),len=strlen(s+1);
		for(int i=1;i<=len;++i){
			pre1[i]=add(s[i]-'a'+1,mul(pre1[i-1],base1));
			pre2[i]=add(s[i]-'a'+1,mul(pre2[i-1],base2));
		}
		for(int i=len;i>=1;--i){
			suf1[i]=add(s[i]-'a'+1,mul(base1,suf1[i+1]));
			suf2[i]=add(s[i]-'a'+1,mul(base2,suf2[i+1]));
		}
	}
	inline puu Hash(int l,int r){
		if(l<=r) return mp(dec(pre1[r],mul(pre1[l-1],p1[r-l+1])),dec(pre2[r],mul(pre2[l-1],p2[r-l+1])));
		return mp(dec(suf1[r],mul(suf1[l+1],p1[l-r+1])),dec(suf2[r],mul(suf2[l+1],p2[l-r+1])));
	}
}s1,s2,w;
inline void prework(){
	p1[0]=p2[0]=1;
	for(int i=1;i<maxn;++i) p1[i]=mul(p1[i-1],base1),p2[i]=mul(p2[i-1],base2);
	s1.read(),s2.read(),w.read();
}
inline void solve(int M,int ret=0){
	if(M==1){
		for(int i=1;i<=n;++i){
			if(s1.s[i]==w.s[1]) ++ret;
			if(s2.s[i]==w.s[1]) ++ret;
		}
	}
	if(M==2){
		for(int i=1;i<=n;++i){
			if(s1.s[i]==w.s[1]&&s2.s[i]==w.s[2]) ++ret;
			if(s2.s[i]==w.s[1]&&s1.s[i]==w.s[2]) ++ret;
		}
		for(int i=1;i<n;++i){
			if(s1.s[i]==w.s[1]&&s1.s[i+1]==w.s[2]) ++ret;
			if(s1.s[i+1]==w.s[1]&&s1.s[i]==w.s[2]) ++ret;
			if(s2.s[i]==w.s[1]&&s2.s[i+1]==w.s[2]) ++ret;
			if(s2.s[i+1]==w.s[1]&&s2.s[i]==w.s[2]) ++ret;
		}
	}printf("%d\n",ret);
}
inline bool operator==(const puu &a,const puu &b){return a.first==b.first&&a.second==b.second;}
int main(){
	//freopen("string.in","r",stdin);
	prework(),n=s1.len,m=w.len;
	if(m<=2) return solve(m),0;
	f[0][n+1][0]=f[1][n+1][0]=1;
	for(int i=n;i>=1;--i){
		f[0][i][0]=f[1][i][0]=1;
		for(int k=2;(k+k<=m)&&(i+k-1<=n);++k){
			puu t1=s1.Hash(i,i+k-1),t2=s2.Hash(i,i+k-1),t3=w.Hash(m,m-k+1),t4=w.Hash(m-k-k+1,m-k);
			if (t1==t3&&t2==t4) f[1][i][k+k]=1;
			if (t2==t3&&t1==t4) f[0][i][k+k]=1;
		}
	}
	for(int i=n;i>=1;--i){
		for(int k=1;k<=m;++k){
			if(s1.s[i]==w.s[m-k+1]) Add(f[0][i][k],f[0][i+1][k-1]);
			if(s2.s[i]==w.s[m-k+1]) Add(f[1][i][k],f[1][i+1][k-1]);
			if(k>1&&s1.s[i]==w.s[m-k+1]&&s2.s[i]==w.s[m-k+2]) Add(f[0][i][k],f[1][i+1][k-2]);
			if(k>1&&s2.s[i]==w.s[m-k+1]&&s1.s[i]==w.s[m-k+2]) Add(f[1][i][k],f[0][i+1][k-2]);
		}
	}
	for(int i=1;i<=n+1;++i){
		Add(ans,add(f[0][i][m],f[1][i][m]));
		for(int k=2;(k+k<=m)&&(k<i);++k){
			puu t1=s1.Hash(i-k,i-1),t2=s2.Hash(i-k,i-1),t3=w.Hash(k,1),t4=w.Hash(k+1,k+k);
			if (t1==t3&&t2==t4) Add(ans,f[1][i][m-k-k]);
			if (t2==t3&&t1==t4) Add(ans,f[0][i][m-k-k]);
		}
	}
	memset(f,0,sizeof f),f[0][n+1][0]=f[1][n+1][0]=1;
	for(int i=n;i>=1;--i){
		f[0][i][0]=f[1][i][0]=1;
		for(int k=2;(k+k<m)&&(i+k-1<=n);++k){
			puu t1=s1.Hash(i,i+k-1),t2=s2.Hash(i,i+k-1),t3=w.Hash(1,k),t4=w.Hash(k+k,k+1);
			if(t1==t3&&t2==t4) f[1][i][k+k]=1;
			if(t1==t4&&t2==t3) f[0][i][k+k]=1;
		}
	}
	for(int i=n;i>=1;--i){
		for(int k=1;k<=m;++k){
			if (s1.s[i]==w.s[k]) Add(f[0][i][k],f[0][i+1][k-1]);
			if (s2.s[i]==w.s[k]) Add(f[1][i][k],f[1][i+1][k-1]);
			if (k>1&&s1.s[i]==w.s[k]&&s2.s[i]==w.s[k-1]) Add(f[0][i][k],f[1][i+1][k-2]);
			if (k>1&&s2.s[i]==w.s[k]&&s1.s[i]==w.s[k-1]) Add(f[1][i][k],f[0][i+1][k-2]);
		}
	}
	for(int i=1;i<=n+1;++i){
		Add(ans,add(f[0][i][m],f[1][i][m]));
		for(int k=2;(k+k<m)&&(k<i);++k){
			puu t1=s1.Hash(i-k,i-1),t2=s2.Hash(i-k,i-1),t3=w.Hash(m-k+1,m),t4=w.Hash(m-k,m-k-k+1);
			if(t1==t3&&t2==t4) Add(ans,f[1][i][m-k-k]);
			if(t1==t4&&t2==t3) Add(ans,f[0][i][m-k-k]);
		}
	}printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值