题解-路径数+算法-回荡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;
}
祝大家学习愉快!