題解-路徑數+算法-迴盪dp
Description
題目背景
到一個 的藥草田裏採藥,她從左上角的格子田(第一行,第一列)出發,要到達右下角(第 行,第 列)的格子田,每次她可以走到與當前格子有邊相鄰的格子去,但她不會走已經走過的格子,而且出於對美的要求,她走過的路徑是關於 左下-右上 對角線對稱的。由於地勢不同,在每個格子田採藥都會有一個疲勞度 , 想知道:有多少條合法路徑,可以使得她採藥的疲勞度最小。
輸入格式:
多組測試數據。
每組數據第一行一個整數 ,接下來 行,每行 個非零數字( 中一個),表示格子田的疲勞度。
當 ,輸入結束。
輸出格式:
對於每組數據,輸出一個整數表示答案,答案。
樣例輸入:2 1 1 1 1 3 1 1 1 1 1 1 2 1 1 0
樣例輸出:
2 3
數據範圍:
對於 的數據滿足 。
對於另外 的數據滿足 。
對於 的數據滿足 ,不超過 組數據。
Introduction
這題很容易騙分:
如果用 暴力枚舉路徑騙分,可以騙到 。
如果用錯誤的思路 來 ,也能騙到 (只往右下角走,不回頭)。
Solution
先找路徑最小權值
首先因爲要從左下角走到右下角並且路線根據左下-右上的對角線對稱,所以相對於每個格子的權值爲:
然後對於 的格子就不用考慮了,只需要考慮從 左上角走到 的格子的最小權值路徑即可。
找最短路徑要用到的算法是:迴盪 。
因爲該算法是本蒟蒻考場上急中生智想出來的,所以就給它取了個奇怪的名字。
因爲題目並沒有說只能像下-左走,所以可能出現如下噁心數據:
10
1 1 1 1 1 9 9 9 9 9
9 9 9 9 1 9 9 9 9 9
9 9 1 1 1 9 9 9 9 9
9 9 1 9 9 9 9 9 9 9
9 9 1 1 1 1 9 9 9 9
9 9 9 9 9 1 9 1 1 1
9 9 9 9 9 1 9 1 9 1
9 9 9 9 9 1 1 1 9 1
9 9 9 9 9 9 9 9 9 1
9 9 9 9 9 9 9 9 9 1
0
這時的最小權值路徑應該是沿着圖中的 走。如果用普通的 ,就會很難找到一個合適的遞推順序。
但是因爲它這樣拐的彎最多隻有 個,所以可以考慮這樣做:
重複 次
從左上角到對稱軸順序 。
從對稱軸到左上角逆序 。
這樣就保證可以覆蓋到任何路徑了。記 表示 到 的路徑最小權值,所以這裏可以有一個小的優化,如果一次正序和一次逆序 都沒有改變任何 的值,則停止重複正序逆序迴盪 。
Code:
//...
bool zxf(){//左上角到對稱軸
bool res=0;
for(int i=3;i<=n+1;i++)
for(int x=1;x<i;x++){
int y=i-x;
if(min(f[x][y-1],f[x-1][y])+val(x,y)<f[x][y])
f[x][y]=min(f[x][y-1],f[x-1][y])+val(x,y),res=1;
}
return res;
}
bool fxf(){//對稱軸到左上角
bool res=0;
for(int i=n;i>=2;i--)
for(int x=1;x<i;x++){
int y=i-x;
if(min(f[x][y+1],f[x+1][y])+val(x,y)<f[x][y])
f[x][y]=min(f[x][y+1],f[x+1][y])+val(x,y),res=1;
}
return res;
}
//...
void dp(){
//...
memset(f,0x3f,sizeof f);
f[1][1]=val(1,1);
for(int i=1;i<=n;i++)
if(!zxf()&&!fxf()) break;//空迴盪優化
//...
}
//...
然後統計最小權值路徑數
思路類似,記 表示從 到對稱軸 上最小權值路徑的條數。
首先找到 。找到所有滿足 的 ,令 。然後因爲最短路徑也會是繞來繞去的,所以再次迴盪 找最短路線經過的格子:
重複 次
從對稱軸到左上角正序逆推 。
從左上角到對稱軸逆序逆推 。
然後因爲這裏不是求最小值,容易重複計算路徑,所有應用類似網絡流的思想,記 表示 這個格子在 方向上已經遞推了的 值,如果新一輪迴蕩中所有 的值都沒有改變,就優化——停止迴盪。
Code:
//...
bool zxg(){//從左上角到對稱軸逆序逆推dp
bool res=0;
for(int i=3;i<=n+1;i++)
for(int x=1;x<i;x++){
int y=i-x;
if(f[x][y]+val(x-1,y)==f[x-1][y]&&g[x-1][y]>fw[x][y][0])
(g[x][y]+=g[x-1][y]-fw[x][y][0])%=mod,fw[x][y][0]=g[x-1][y],res=1;
if(f[x][y]+val(x,y-1)==f[x][y-1]&&g[x][y-1]>fw[x][y][1])
(g[x][y]+=g[x][y-1]-fw[x][y][1])%=mod,fw[x][y][1]=g[x][y-1],res=1;
}
return res;
}
bool fxg(){//從對稱軸到左上角正序逆推dp。
bool res=0;
for(int i=n;i>=2;i--)
for(int x=1;x<i;x++){
int y=i-x;
if(f[x][y]+val(x+1,y)==f[x+1][y]&&g[x+1][y]>fw[x][y][2])
(g[x][y]+=g[x+1][y]-fw[x][y][2])%=mod,fw[x][y][2]=g[x+1][y],res=1;
if(f[x][y]+val(x,y+1)==f[x][y+1]&&g[x][y+1]>fw[x][y][3])
(g[x][y]+=g[x][y+1]-fw[x][y][3])%=mod,fw[x][y][3]=g[x][y+1],res=1;
}
return res;
}
void dp(){
//...
mn=inf;
for(int i=1;i<=n;i++)
mn=min(mn,f[i][n+1-i]);
memset(g,0x00,sizeof g);
for(int i=1;i<=n;i++)
if(f[i][n+1-i]==mn) g[i][n+1-i]=1;
memset(fw,0x00,sizeof fw);
for(int i=1;i<=n;i++)
if(!fxg()&&!zxg()) break;
//...
}
//...
最後答案就是 ,即 到對稱軸上最小權值路徑格子的路徑數。即 到 的最小 和路徑條數。時間複雜度是 ,如果有優化,應該 的數據也過得了。
Code
#include <bits/stdc++.h>
using namespace std;
//@Start
const int inf=0x3f3f3f3f;
//@Debug
void debug(int x,int y,int arr[][1010]){
for(int i=1;i<=x;i++)
for(int j=1;j<=y;j++)
printf("%d%c",arr[i][j],"\n "[j<y]);
}
//@DP
const int N=1010,mod=1e9+9;
int n,a[N][N],f[N][N],g[N][N],mn,fw[N][N][4];
int val(int x,int y){
if(x+y==n+1) return a[x][y];
return a[x][y]+a[n-y+1][n-x+1];
}
bool zxf(){
bool res=0;
for(int i=3;i<=n+1;i++)
for(int x=1;x<i;x++){
int y=i-x;
if(min(f[x][y-1],f[x-1][y])+val(x,y)<f[x][y])
f[x][y]=min(f[x][y-1],f[x-1][y])+val(x,y),res=1;
}
return res;
}
bool fxf(){
bool res=0;
for(int i=n;i>=2;i--)
for(int x=1;x<i;x++){
int y=i-x;
if(min(f[x][y+1],f[x+1][y])+val(x,y)<f[x][y])
f[x][y]=min(f[x][y+1],f[x+1][y])+val(x,y),res=1;
}
return res;
}
bool zxg(){
bool res=0;
for(int i=3;i<=n+1;i++)
for(int x=1;x<i;x++){
int y=i-x;
if(f[x][y]+val(x-1,y)==f[x-1][y]&&g[x-1][y]>fw[x][y][0])
(g[x][y]+=g[x-1][y]-fw[x][y][0])%=mod,fw[x][y][0]=g[x-1][y],res=1;
if(f[x][y]+val(x,y-1)==f[x][y-1]&&g[x][y-1]>fw[x][y][1])
(g[x][y]+=g[x][y-1]-fw[x][y][1])%=mod,fw[x][y][1]=g[x][y-1],res=1;
}
return res;
}
bool fxg(){
bool res=0;
for(int i=n;i>=2;i--)
for(int x=1;x<i;x++){
int y=i-x;
if(f[x][y]+val(x+1,y)==f[x+1][y]&&g[x+1][y]>fw[x][y][2])
(g[x][y]+=g[x+1][y]-fw[x][y][2])%=mod,fw[x][y][2]=g[x+1][y],res=1;
if(f[x][y]+val(x,y+1)==f[x][y+1]&&g[x][y+1]>fw[x][y][3])
(g[x][y]+=g[x][y+1]-fw[x][y][3])%=mod,fw[x][y][3]=g[x][y+1],res=1;
}
return res;
}
void dp(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
memset(f,0x3f,sizeof f);
f[1][1]=val(1,1);
for(int i=1;i<=n;i++)
if(!zxf()&&!fxf()) break;
// debug(n,n,f);
mn=inf;
for(int i=1;i<=n;i++)
mn=min(mn,f[i][n+1-i]);
memset(g,0x00,sizeof g);
for(int i=1;i<=n;i++)
if(f[i][n+1-i]==mn) g[i][n+1-i]=1;
memset(fw,0x00,sizeof fw);
for(int i=1;i<=n;i++)
if(!fxg()&&!zxg()) break;
printf("%d\n",g[1][1]);
}
//@Main
int main(){
// freopen("100.in","r",stdin);
scanf("%d",&n);
while(n) dp(),scanf("%d",&n);//多組測試數據
return 0;
}
祝大家學習愉快!