題解-路徑數+算法-迴盪dp

題解-路徑數+算法-迴盪dp

Description

題目背景
Euphemia\texttt{Euphemia} 到一個 N×NN\times N 的藥草田裏採藥,她從左上角的格子田(第一行,第一列)出發,要到達右下角(第 NN 行,第 NN 列)的格子田,每次她可以走到與當前格子有邊相鄰的格子去,但她不會走已經走過的格子,而且出於對美的要求,她走過的路徑是關於 左下-右上 對角線對稱的。由於地勢不同,在每個格子田採藥都會有一個疲勞度 Ti,jT_{i,j}Euphemia\texttt{Euphemia} 想知道:有多少條合法路徑,可以使得她採藥的疲勞度最小。
輸入格式:
多組測試數據。
每組數據第一行一個整數 NN,接下來 NN 行,每行 NN 個非零數字(1,2,3...91,2,3...9 中一個),表示格子田的疲勞度。
N=0N=0,輸入結束。
輸出格式:
對於每組數據,輸出一個整數表示答案,答案%1000000009\%1000000009
樣例輸入:

2
1 1
1 1
3
1 1 1
1 1 1
2 1 1
0

樣例輸出:

2
3

數據範圍:
對於 20%20\% 的數據滿足 N5N\le5
對於另外 20%20\% 的數據滿足 N40N\le40
對於 100%100\% 的數據滿足 N100N\le100,不超過 5050 組數據。


Introduction

這題很容易騙分:

如果用 dfs\texttt{dfs} 暴力枚舉路徑騙分,可以騙到 20分\color{#f34c05}\texttt{20分}

如果用錯誤的思路 Θ(n2)\Theta(n^2)dp\texttt{dp},也能騙到 60分\color{#ffcc00}\texttt{60分}(只往右下角走,不回頭)。

Solution

先找路徑最小權值

首先因爲要從左下角走到右下角並且路線根據左下-右上的對角線對稱,所以相對於每個格子的權值爲:

vali,j={Ti,j+TNj+1,Ni+1(i+j<N+1)Ti,j(i+j==N+1) val_{i,j}= \begin{cases} T_{i,j}+T_{N-j+1,N-i+1}(i+j<N+1)\\ T_{i,j}(i+j==N+1) \end{cases}

然後對於 i+j>N+1i+j>N+1 的格子就不用考慮了,只需要考慮從 (1,1)(1,1) 左上角走到 i+j==N+1i+j==N+1 的格子的最小權值路徑即可。


找最短路徑要用到的算法是:迴盪 dp\texttt{dp}

因爲該算法是本蒟蒻考場上急中生智想出來的,所以就給它取了個奇怪的名字。

因爲題目並沒有說只能像下-左走,所以可能出現如下噁心數據:

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

這時的最小權值路徑應該是沿着圖中的 11 走。如果用普通的 dp\texttt{dp},就會很難找到一個合適的遞推順序。

但是因爲它這樣拐的彎最多隻有 nn 個,所以可以考慮這樣做:

重複 nn

從左上角到對稱軸順序 dp\texttt{dp}
從對稱軸到左上角逆序 dp\texttt{dp}

這樣就保證可以覆蓋到任何路徑了。記 fi,j(i+jN+1)f_{i,j}(i+j\le N+1) 表示 (1,1)(1,1)(i,j)(i,j) 的路徑最小權值,所以這裏可以有一個小的優化,如果一次正序和一次逆序 dp\texttt{dp} 都沒有改變任何 fi,jf_{i,j} 的值,則停止重複正序逆序迴盪 dp\texttt{dp}

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;//空迴盪優化
	//...
}
//...

然後統計最小權值路徑數

思路類似,記 gi,j(i+jN+1)g_{i,j}(i+j\le N+1) 表示從 (i,j)(i,j) 到對稱軸 (x,y)(x+y==N+1)(x,y)(x+y==N+1) 上最小權值路徑的條數。

首先找到 mn=min{fi,j}(i+j==N+1)mn=\min\{f_{i,j}\}(i+j==N+1)。找到所有滿足 fi,j(i+j==N+1)==mnf_{i,j}(i+j==N+1)==mn(i,j)(i,j),令 gi,j=1g_{i,j}=1。然後因爲最短路徑也會是繞來繞去的,所以再次迴盪 dp\texttt{dp} 找最短路線經過的格子:

重複 nn

從對稱軸到左上角正序逆推 dp\texttt{dp}
從左上角到對稱軸逆序逆推 dp\texttt{dp}

然後因爲這裏不是求最小值,容易重複計算路徑,所有應用類似網絡流的思想,記 fwi,j,k(i+jN+1,k{0,1,2,3})fw_{i,j,k}(i+j\le N+1,k\in\{0,1,2,3\}) 表示 (i,j)(i,j) 這個格子在 kk 方向上已經遞推了的 gg 值,如果新一輪迴蕩中所有 fwi,j,kfw_{i,j,k} 的值都沒有改變,就優化——停止迴盪。

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;
	//...
}
//...

最後答案就是 g1,1g_{1,1},即 (1,1)(1,1) 到對稱軸上最小權值路徑格子的路徑數。即 (1,1)(1,1)(N,N)(N,N) 的最小 Ti,jT_{i,j} 和路徑條數。時間複雜度是 Θ(N3)\Theta(N^3),如果有優化,應該 N=1000N=1000 的數據也過得了。

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;
}

祝大家學習愉快!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章