题目:给出一个字母矩阵,问从左上角走到右下角的走法有多少种走法能走出回文子串,只能向下或向右走。
思路:正解是O(n^3)的dp,也跑了2.9s,可见这题时限是比较紧的。自然的一个想法是两头同时向中间走,这样可以保证走出的是回文串,问题在于如何dp。一共需要走O(n+m)步,而每一步从左上走过来的和右下走过来的进行匹配,以行数作为下标表示二者即可(因为距离确定的情况下只需要知道一个坐标)。所以dp[i][j][k]就表示在第i步,第j行和第k行配对组成的回文串的数目,滚动数组优化空间把步数那一维去掉。转移的时候需要考虑四种走法(对应两头的两种走法相乘)。然后就是递推到中间之后,根据x和y轴的长度之和还需要稍微讨论一下。
#include <bits/stdc++.h> #define pb push_back #define se second #define fs first #define sq(x) (x)*(x) #define eps 0.000000001 using namespace std; typedef long long ll; typedef pair<ll,ll> P; const ll mod=1e9+7; const int maxv=505; ll dp[2][maxv][maxv]; int n,m; char r[maxv]; char mp[maxv][maxv]; const int d1[2]={1,0}; const int d2[2]={0,1}; const int p1[2]={-1,0}; const int p2[2]={0,-1}; bool out(int x,int y){ return x<0||x>=n||y<0||y>=m; } int main(){ // freopen("/home/files/CppFiles/in","r",stdin); cin>>n>>m; for(int i=0;i<n;i++){ scanf("%s",r); for(int j=0;j<m;j++){ mp[i][j]=r[j]; } } if(mp[0][0]!=mp[n-1][m-1]){ cout<<0<<endl; return 0; } bool f=1; dp[0][0][n-1]=1; bool bbb=1; for(int d=0;d<(n+m-2)/2;d++){ f^=1; for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ dp[f^1][i][j]=0; } } for(int i=0;i<n;i++){///up for(int j=0;j<n;j++){///down int x1=i,y1=d-x1; int x2=j,y2=n+m-2-d-x2; if(out(x1,y1)||out(x2,y2)) continue; for(int p=0;p<2;p++){ for(int q=0;q<2;q++){ int dx1=x1+d1[p],dy1=y1+d2[p]; int dx2=x2+p1[q],dy2=y2+p2[q]; if(out(dx1,dy1)||out(dx2,dy2)) continue; if(mp[dx1][dy1]==mp[dx2][dy2]){ dp[f^1][dx1][dx2]=(dp[f^1][dx1][dx2]+dp[f][x1][x2])%mod; } } } } } ll ans=0; for(int i=0;i<n;i++){ if(i+1<n&&(n+m)%2==1) ans=(ans+dp[f^1][i][i+1])%mod; ans=(ans+dp[f^1][i][i])%mod; } cout<<ans<<endl; return 0; } }