【CF613E】Puzzle Lover(麻烦的DP题)
- 给定一个\(2\times n\)的字符矩阵和一个长为\(m\)的模式串。
- 问矩阵中有多少条长为\(m\)的路径,不经过重复位置,且走过的字符依次拼接能得到给定模式串。
- \(n,m\le2\times10^3\)
初步分析
其实这道题大概在若干年前(?)听某个学长讲课讲过,不知道为什么至今仍留有一些印象,所以稍微推了一下就自己做出来了(听过讲解再做出来真的能算自己做出来的吗。。。)。
假设起点在终点左边。
考虑一条不经过重复位置的路径必然形如下图:
即,开头和结尾各有一段回旋(红、蓝),中间则是自由上上下下(绿)。(注意,回旋可能会退化成一个点)
显然我们可以先通过哈希来预处理出\(p_{o,i,j}\)和\(q_{o,i,j}\),分别表示以\((o,i)\)为端点,能否形成向左的长度为\(2\times j\)的回旋(每行各\(j\))和向右的长度为\(2\times j\)的回旋。
动态规划
\(DP\)就考虑设\(f_{o,i,j}\)表示走到\((o,i)\)时长度为\(j\)的方案数。
转移显然有四种情况:单独作为一个起点(只出现在\(j=1\)时)、作为向左回旋的结尾、从左转移、从同列转移。其中同列转移要注意防止在同列不断上下乱跳,可以考虑从同列格子的左边转移。
统计答案就考虑一个位置能否作为向右回旋的开头或单独作为一个终点(只出现在\(j=m\)时)即可。
由于我们前面假设了起点在终点左边,还需要翻转模式串再做一遍就可以解决这个问题。
但是要注意一下如果整条路径就是一个回旋的情况容易算漏或算重,反正我的做法会算漏整个左回旋,算重整个右回旋。
口胡起来反正很容易,具体实现细节死多。
代码:\(O(n^2)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define X 1000000007
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int n,m,f[2][N+5][N+5],g[2][N+5],p[2][N+5][N+5],q[2][N+5][N+5];char w[N+5],s[2][N+5];
struct Hash
{
#define ull unsigned long long
#define CU Con ull&
ull x,y;I Hash() {x=y=0;}I Hash(CU a) {x=y=a;}I Hash (CU a,CU b):x(a),y(b){}
I Hash operator + (Con Hash& o) Con {return Hash(x+o.x,y+o.y);}
I Hash operator - (Con Hash& o) Con {return Hash(x-o.x,y-o.y);}
I Hash operator * (Con Hash& o) Con {return Hash(x*o.x,y*o.y);}
I bool operator == (Con Hash& o) Con {return x==o.x&&y==o.y;}
}seed(20050413,20201118),P[2*N+5],U[N+5],V[N+5],S[2][N+5];
#define G1(H,l,r) (H[r]-H[(l)-1])//取出正哈希的[l,r]
#define G2(H,l,r) (H[l]-H[(r)+1])//取出逆哈希的[l,r]
int ans;I void Solve(CI tg)
{
RI i,j,o;for(i=1;i<=m;++i) U[i]=U[i-1]+P[i]*w[i];for(i=m;i;--i) V[i]=V[i+1]+P[m-i+1]*w[i];//预处理模式串的正逆哈希
for(o=0;o<=1;++o) for(i=1;i<=n;++i) for(j=1;2*j<=m&&j<=i;++j) p[o][i][j<<1]=//预处理每个点的左回旋
G1(S[o^1],i-j+1,i)*P[m]==G2(V,1,j)*P[i]&&G1(S[o],i-j+1,i)*P[j<<1]==G1(U,j+1,j<<1)*P[i];
for(o=0;o<=1;++o) for(i=1;i<=n;++i) for(j=1;2*j<=m&&j<=n-i+1;++j) q[o][i][j<<1]=//预处理每个点的右回旋
G1(S[o^1],i,i+j-1)==G2(V,m-j+1,m)*P[i-1]&&G1(S[o],i,i+j-1)*P[m-j+1]==G1(U,m-2*j+1,m-j)*P[i+j];
for(memset(f,0,sizeof(f)),i=1;i<=n;++i)//DP中间过程
{
for(j=m;j^2;--j) for(o=0;o<=1;++o) s[o][i]==w[j]&&Inc(f[o][i][j],
0LL+p[o][i][j]+f[o][i-1][j-1]+(s[o^1][i]==w[j-1]?f[o^1][i-1][j-2]:0));//三种转移
f[0][i][1]=s[0][i]==w[1],f[1][i][1]=s[1][i]==w[1],//特殊转移j=1
s[0][i]==w[2]&&(f[0][i][2]=p[0][i][2]+f[0][i-1][1]),//特殊转移j=2
s[1][i]==w[2]&&(f[1][i][2]=p[1][i][2]+f[1][i-1][1]);//特殊转移j=2
}
for(o=0;o<=1;++o) for(i=1;i<=n;++i) for(j=1;j<=m;++j)//统计答案
s[o][i]==w[j]&&(q[o][i][m-j+1]||j==m)&&Inc(ans,j^1?f[o][i-1][j-1]:1);
if(tg) for(o=0;o<=1;++o) for(i=1;i<=n;++i) p[o][i][m]&&++ans,q[o][i][m]&&--ans;//特殊处理整个回旋
}
int main()
{
RI i,o;scanf("%s%s%s",s[0]+1,s[1]+1,w+1),n=strlen(s[0]+1),m=strlen(w+1);
if(m==1) {for(i=1;i<=n;++i) ans+=(s[0][i]==w[1])+(s[1][i]==w[1]);return printf("%d\n",ans),0;}//特判m=1
if(m==2) {for(o=0;o<=1;++o) for(i=1;i<=n;++i) s[o][i]==w[1]&&
(ans+=(s[o][i-1]==w[2])+(s[o][i+1]==w[2])+(s[o^1][i]==w[2]));return printf("%d\n",ans),0;}//特判m=2
for(P[0]=i=1;i<=n+m;++i) P[i]=P[i-1]*seed;//预处理哈希种子的幂
for(i=1;i<=n;++i) S[0][i]=S[0][i-1]+P[i]*s[0][i],S[1][i]=S[1][i-1]+P[i]*s[1][i];//预处理字符矩阵的哈希
return Solve(0),reverse(w+1,w+m+1),Solve(1),printf("%d\n",ans),0;//注意翻转模式串再做一遍
}
待到再迷茫时回头望,所有脚印会发出光芒