CF613E 题解

题意:给出一个 2 × n 2\times n 2×n的矩阵 A A A,每个元素均为小写字母。再给出一个字符串 s s s,问矩阵中有多少路径满足:
1. 1. 1. 路径上的字母连起来为 s s s
2. 2. 2.不能多次经过同一位置。
3. 3. 3.只能向上下左右四个方向移动。

n , ∣ s ∣ ≤ 2000 n,|s|\leq 2000 n,s2000,答案对 1 e 9 + 7 1e9+7 1e9+7取模。

前置知识:哈希,动态规划。

对于在 2 × n 2\times n 2×n的矩阵上的问题,一种常见的思路就是把一条路径拆分成多段,对每段分别求解再合并,有时需要用数据结构维护。那么对于这题,不难发现:任何一条路径都可以被划分成三段,如图所示:
在这里插入图片描述
1. 1. 1.蓝色部分:起点和起点左侧的部分。对于这一部分,考虑枚举起点。这一部分要么向左延伸后再回来,要么只包含起点一个点。考虑用 H a s h Hash Hash判断这段路径是否合法,复杂度 O ( n 2 ) O(n^2) O(n2)
2. 2. 2.红色部分:起点右侧,终点左侧的部分。这部分一眼看上去不是很好求,考虑用 D P DP DP计算答案。设 f 0 / 1 , i , j , k f_{0/1,i,j,k} f0/1,i,j,k表示现在在 ( i , j ) (i,j) (i,j),匹配到字符串 s s s的第 k k k位,第一维为 0 0 0表示直接从上一列过来,没有经过 ( i ⊕ 1 , j ) (i\oplus 1,j) (i1,j),第一维为 1 1 1表示经过 ( i ⊕ 1 , j ) (i\oplus 1,j) (i1,j) ( i , j ) (i,j) (i,j)
则有转移方程:
f 0 , i , j , k = f 0 , i , j − 1 , k − 1 + f 1 , i , j − 1 , k − 1 f_{0,i,j,k}=f_{0,i,j-1,k-1}+f_{1,i,j-1,k-1} f0,i,j,k=f0,i,j1,k1+f1,i,j1,k1 ( A i , j = s k ) (A_{i,j}=s_k) (Ai,j=sk)
f 1 , i , j , k = f 0 , i ⊕ 1 , j − 1 , k − 2 + f 1 , i ⊕ 1 , j − 1 , k − 2 f_{1,i,j,k}=f_{0,i\oplus 1,j-1,k-2}+f_{1,i\oplus 1,j-1,k-2} f1,i,j,k=f0,i1,j1,k2+f1,i1,j1,k2 ( A i , j = s k , A i ⊕ 1 , j = s k − 1 ) (A_{i,j}=s_k,A_{i\oplus 1,j}=s_{k-1}) (Ai,j=sk,Ai1,j=sk1)
初始化就是通过枚举蓝色部分路径给 D P DP DP赋初值。复杂度 O ( n 2 ) O(n^2) O(n2)
3. 3. 3.绿色部分:终点和终点右侧的部分。枚举路径和蓝色部分相似,若合法则将前面对应的 D P DP DP值加入答案。

一些细节:
1. 1. 1.注意到起点可能在终点的右侧,要将 s s s翻转后再算一次。
2. 2. 2.可能三段路径中有两段都为空,而这些路径会被重复算一次,所以这部分应单独计算。
3. 3. 3. ∣ s ∣ ≤ 2 |s|\leq 2 s2时,需要特判,这里不再赘述。
4. 4. 4.考虑到 C F CF CF数据比较强,建议使用双哈希。
代码:

#include<iostream>
#include<string.h>
#include<algorithm>
#define gc getchar
#define pc putchar
#define pb push_back
#define f first
#define s second
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
template<class Typ> Typ &Rd(Typ &x){
	char ch=gc(),sgn=0; x=0;
	for(;ch<'0'||ch>'9';ch=gc()) sgn|=ch=='-';
	for(;ch>='0'&&ch<='9';ch=gc()) x=x*10+(ch^48);
	return sgn&&(x=-x),x;
}
template<class Typ> void Wt(Typ x){
	if(x<0) pc('-'),x=-x;
	if(x>9) Wt(x/10);
	pc(x%10^48);
}
const int N=2e3+5;
const int Md=1e9+7;
const LL B1=131,B2=237;
const LL M1=998244353,M2=1000000123;
struct HASH{LL H1,H2;}H1[2][N],H2[2][N],H[N];
int n,F[2][2][N][N],ans,len,cur;
LL P1[N],P2[N]; char s[N],A[2][N];
bool operator ==(HASH A,HASH B){return A.H1==B.H1&&A.H2==B.H2;}
HASH operator+(HASH A,HASH B){return {(A.H1+B.H1)%M1,(A.H2+B.H2)%M2};}
HASH operator-(HASH A,HASH B){return {(A.H1-B.H1+M1)%M1,(A.H2-B.H2+M2)%M2};}
HASH operator*(HASH A,HASH B){return (HASH){A.H1*B.H1%M1,A.H2*B.H2%M2};}
HASH GET1(int op,int l,int r){return H1[op][r]-H1[op][l-1]*(HASH){P1[r-l+1],P2[r-l+1]};}
HASH GET2(int op,int l,int r){return H2[op][l]-H2[op][r+1]*(HASH){P1[r-l+1],P2[r-l+1]};}
HASH GET(int l,int r){return H[r]-H[l-1]*(HASH){P1[r-l+1],P2[r-l+1]};}
void SPJ(){
	for(int i=0;i<=1;i++)
		for(int j=1;j<=n;j++)
			ans+=A[i][j]==s[1];
	Wt(ans),exit(0);
}
void GETANS(){
	memset(F,0,sizeof(F));
	for(int i=1;i<=len;i++)
		H[i]=H[i-1]*(HASH){B1,B2}\
		+(HASH){s[i]-'a'+1,s[i]-'a'+1};
	for(int i=0;i<=1;i++) for(int j=1;j<=n;j++)
		for(int k=1;k<=j&&2*k<=len;k++)
			if(GET2(i,j-k+1,j)*(HASH){P1[k],P2[k]}\
			+GET1(i^1,j-k+1,j)==GET(1,2*k))
				F[1][i^1][j][2*k]++,(2*k==len)&&cur++;
	for(int i=0;i<=1;i++) for(int j=1;j<=n;j++)
		if(A[i][j]==s[1]) F[0][i][j][1]++;
	for(int k=1;k<=len;k++) for(int j=1;j<=n;j++)
		for(int i=0;i<=1;i++) if(A[i][j]==s[k]){
			(F[0][i][j][k]+=F[0][i][j-1][k-1])%=Md;
			(F[0][i][j][k]+=F[1][i][j-1][k-1])%=Md;
			if(k>1&&A[i^1][j]==s[k-1]){
				(F[1][i][j][k]+=F[0][i^1][j-1][k-2])%=Md;
				(F[1][i][j][k]+=F[1][i^1][j-1][k-2])%=Md;
			}
		}
	for(int i=0;i<=1;i++) for(int j=1;j<=n;j++)
		for(int k=1;k<=n-j+1&&2*k<=len;k++)
			if(GET1(i^1,j,j+k-1)*(HASH){P1[k],P2[k]}\
			+GET2(i,j,j+k-1)==GET(len-2*k+1,len)){
				(ans+=(F[0][i^1][j-1][len-2*k]+\
				F[1][i^1][j-1][len-2*k])%Md)%=Md;
				if(2*k==len&&len>2) cur++;
			}
	for(int i=0;i<=1;i++) for(int j=1;j<=n;j++)
		if(A[i][j]==s[len]) (ans+=(F[0][i][j-1][len-1]+\
			F[1][i][j-1][len-1])%Md)%=Md;
}
int main(){
	P1[0]=P2[0]=1;
	for(int i=1;i<N;i++) P1[i]=P1[i-1]*B1%M1;
	for(int i=1;i<N;i++) P2[i]=P2[i-1]*B2%M2;
	scanf("%s%s%s",A[0]+1,A[1]+1,s+1);
	n=strlen(A[0]+1),len=strlen(s+1); if(len==1) SPJ();
	for(int i=0;i<=1;i++) for(int j=1;j<=n;j++)
		H1[i][j]=H1[i][j-1]*(HASH){B1,B2}\
		+(HASH){A[i][j]-'a'+1,A[i][j]-'a'+1};
	for(int i=0;i<=1;i++) for(int j=n;j>=1;j--)
		H2[i][j]=H2[i][j+1]*(HASH){B1,B2}\
		+(HASH){A[i][j]-'a'+1,A[i][j]-'a'+1};
	GETANS(),reverse(s+1,s+len+1),GETANS(),Wt((ans+cur/2)%Md);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值