P3126 [USACO15OPEN]迴文的路徑Palindromic Pathsios
看到這題題解很少,蒟蒻便想更加通俗易懂地分享一點本身的心得,歡迎大佬批評指正^_^數組
像這種棋盤形的兩邊同時作的dp還有
P1006 傳紙條,
P1004 方格取數,
T35377 大教室中傳紙條優化
對於這種題目最暴力的方法無非是分別枚舉左上角和右下角兩點座標spa
(f[ i ][ j ][ k ][ l ] = f[ i-1 ][ j ][ k+1 ][ l ] + f[ i-1 ][ j ][ k ][ l+1 ] + f[ i ][ j-1 ][ k+1 ][ l ] + f[ i ][ j-1 ][ k ][ l+1 ])code
一塊兒往中間走,即當兩個點重合時便有了路徑——O(n^4),像這種數據較大的題會爆ci
因而便有了新的思路,因爲兩點是一塊兒走的,步數相同,因此能夠枚舉步數,又由於橫縱座標之和等於所走路徑長+1(橫縱座標會重合一點,能夠看下圖理解),因此只需枚舉兩點的橫座標(j,k)就能夠求出兩點的縱座標get
1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|
1 | 左上角 | E | |||
2 | D | ||||
3 | C | ||||
4 | B | ||||
5 | A | 右下角 |
i表示步數(注:左上角和右下角只有一種走法,咱們能夠從第二步開始走,又由於橫縱座標重合一點,爲了使橫座標+縱座標=i,咱們能夠從3開始枚舉)io
上圖字母是路徑長爲5,i(所枚舉的步數)爲5+1=6的狀況table
設j爲左邊點的橫座標(縱座標爲i-j),k爲右邊點所走路徑的豎直長【如上圖點A,枚舉到它時k爲1,橫座標爲n-(i-k)+1=n-i+k+1=5-6+1+1=1,縱座標爲n-k+1=5-1+1=5】class
即f(i,j,k)=f(i-1,j,k)+f(i-1,j-1,k)+f(i-1,j,k-1)+f(i-1,j-1,k-1)【j-1,而i不變,說明點的縱座標+1,其實這個式子與上面暴力的式子是同樣的】
然而,這題數據範圍到了500,若是開500^3的數組會MLE,考慮到每次狀態轉移只需用到f(i-1,j,k),能夠用滾動數組優化空間
逆序枚舉j、k【f[j][k]=f[j][k]+f[j-1][k-1]+f[j-1][k]+f[j][k-1]等式左側步數爲i,而右側實際上是上次枚舉的狀態,步數爲i-1】(與01揹包的滾動數組優化同理)便可避免覆蓋還未轉移的狀態。
只有兩點字母相同時才能走,所以當兩點字母不一樣時方案數爲0,不然爲每一個能夠走過來的狀態的方案數之和
具體細節能夠在代碼中進一步理解
#include<iostream> #define rint register int using namespace std; char a[501][501]; int n; long long f[501][501],ans; int main() { cin>>n; for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) cin>>a[i][j]; if (a[1][1]!=a[n][n]) { cout<<0; return 0; }//若是左上角和右下角字母不一樣則無解 f[1][1]=1;//當兩點分別處於左上角和右下角時方案爲1 for (rint i=3; i<=n+1; i++) for (rint j=i-1; j>=1; j--) for (rint k=i-1; k>=1; k--) { if (a[j][i-j]==a[n-i+k+1][n-k+1])//(用j,k分別求出所對應的橫縱座標)此兩點字母是否相同? f[j][k]=(f[j][k]+f[j-1][k-1]+f[j-1][k]+f[j][k-1])%1000000007; else f[j][k]=0;//不相同則方案爲0 } for (int i=1; i<=n; i++)//統計全部方案數 ans=(ans+f[i][i])%1000000007;//當j=k說明兩點重合 cout<<ans; }