题目链接:传送门
题目大意:
给出一个
N
×
M
N×M
N×M的字符矩阵,求从左上角到右下角、每次只能往右或者往下走,经过的路径上所有字符恰好为一个回文串的方案数。
(
N
,
M
≤
500
)
(N, M≤500)
(N,M≤500)
每次走的时候无后效性,想到使用
d
p
dp
dp。
不妨让左上角和右下角同时开始走。
令
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]表示当前共走
i
i
i次,左上角到第
j
j
j行,右下角到第
k
k
k行,且所有字符满足题意的方案数。
由于走的次数确定,起始点确定,到达的行数确定,
因此珂以算出列数:左上角到第
i
−
j
+
1
i-j+1
i−j+1列,右下角到第
n
+
m
−
i
−
k
+
1
n+m-i-k+1
n+m−i−k+1列。
设
y
1
=
i
−
j
+
1
y1=i-j+1
y1=i−j+1,
y
2
=
n
+
m
−
i
−
k
+
1
y2=n+m-i-k+1
y2=n+m−i−k+1。
当
(
j
,
y
1
)
(j,y1)
(j,y1)与
(
k
,
y
2
)
(k,y2)
(k,y2)的值相等时,珂以从
(
j
,
k
+
1
)
(j,k+1)
(j,k+1)
(
j
−
1
,
k
)
(j-1,k)
(j−1,k)
(
j
−
1
,
k
+
1
)
(j-1,k+1)
(j−1,k+1) 走过来。
然后转移方程就比较显然了,具体见代码。
另外,此题走
i
i
i次的情况只能从走
i
−
1
i-1
i−1次的情况推过来。
因此珂以用滚动数组优化空间。
时间复杂度:
O
(
n
3
)
O(n^3)
O(n3)
空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
丑陋的代码:
#include<stdio.h>
#include<cstring>
#include<algorithm>
#define re register int
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=10*x+ch-'0';
ch=getchar();
}
return x*f;
}
inline char GetChar() {
char ch=getchar();
while(ch<'a' || ch>'z') ch=getchar();
return ch;
}
const int Size=505;
const int INF=0x3f3f3f3f;
const ll mod=1e9+7;
char a[Size][Size];
ll dp[2][Size][Size];
int main() {
int n=read();
int m=read();
for(re i=1; i<=n; i++) {
for(re j=1; j<=m; j++) {
a[i][j]=GetChar();
}
}
//特判一下
if(a[1][1]!=a[n][m]) {
putchar('0');
return 0;
}
dp[1][1][n]=1;
int sum=(n+m)>>1;
for(re i=2; i<=sum; i++) {
for(re j=0; j<=n; j++) {
for(re k=0; k<=n; k++) {
dp[i&1][j][k]=0;
}
}
for(re j=1; j<=n; j++) {
for(re k=n; k>n-i; k--) {
if(j>k) continue;
//y1,y2表示列数
int y1=i-j+1,y2=n+m+1-i-k;
//由于左上角不能向上走,右下角不能向下走,因此y1>y2时就应跳过
if(y1>y2) continue;
if(a[j][y1]!=a[k][y2]) continue;
dp[i&1][j][k]=(dp[!(i&1)][j][k]+dp[!(i&1)][j][k+1]+dp[!(i&1)][j-1][k]+dp[!(i&1)][j-1][k+1])%mod;
}
}
}
ll ans=0;
int stp=sum&1;
for(re i=1; i<=n; i++) {
ans=(ans+dp[stp][i][i])%mod;
if((n+m)&1) {
ans=(ans+dp[stp][i][i+1])%mod;
}
}
printf("%I64d",ans);
return 0;
}