题解-路径数+算法-回荡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;
}

祝大家学习愉快!

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