前言
搜索到这篇文章的朋友,那么很巧了,我们多半是一个学校的,为什么呢?因为这道题叫白银莲花池。。
题目
【问题描述】
FJ建造了一个美丽的池塘,用于让奶牛们锻炼。这个长方形的池子被分割成了 M 行和 N 列(正方形格子的 。某些格子上有莲花,还有一些岩石,其余的只是美丽,纯净,湛蓝的水。
贝茜正在练习芭蕾舞,她从一个莲花跳跃到另一个莲花,当前位于一个莲花。她希望在莲花上一个一个的跳,目标是另一个给定莲花。她不能跳入水中,也不能跳到岩石上。贝茜的每次的跳跃像国际象棋中的骑士一样:横向移动1,纵向移动2,或纵向移动1,横向移动2。所以贝茜有时可能会有多达8个选择的跳跃。
FJ在观察贝茜的芭蕾舞,他意识到有时候贝茜有可能跳不到她想去的目的地,因为路上有些地方没有莲花。于是他想要添加几个莲花使贝茜能够完成任务。一贯节俭的FJ想添加最少数量的莲花。当然,莲花不能放在石头上。
请帮助FJ确定必须要添加的莲花的最少数量。在添加的莲花最少基础上,算出贝茜从起始点跳到目标点需要的最少的步数。最后,还要算出满足添加的莲花的最少数量时,跳跃最少步数的跳跃路径的条数。
【输入格式】
第1行: 两个整数M,N。
第2..M+1 行:第i+1行,第i+1行有N个整数,表示该位置的状态: 0为水; 1为莲花; 2为岩石; 3为贝茜开始的位置; 4为贝茜要去的目标位置.
【输出格式】
第1行: 一个整数: 需要添加的最少的莲花数. 如果无论如何贝茜也无法跳到,输出-1。
第2行: 一个整数:在添加的莲花最少基础上,贝茜从起始点跳到目标点需要的最少的步数。如果第1行输出-1,这行不输出。
第3行: 一个整数: 添加的莲花的最少数量时,跳跃步数为第2行输出的值的跳跃路径的条数 如果第1行输出-1,这行不输出。
【输入样例】
4 8
0 0 0 1 0 0 0 0
0 0 0 0 0 2 0 1
0 0 0 0 0 4 0 0
3 0 0 0 0 0 1 0
【输出样例】
2
6
2
【样例解释】
至少要添加2朵莲花,放在了’x’的位置。
0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0
0 x 0 0 0 2 0 1 0 0 0 0 0 2 0 1
0 0 0 0 x 4 0 0 0 0 x 0 x 4 0 0
3 0 0 0 0 0 1 0 3 0 0 0 0 0 1 0
贝茜至少要跳6步,有以下两种方案
0 0 0 C 0 0 0 0 0 0 0 C 0 0 0 0
0 B 0 0 0 2 0 F 0 0 0 0 0 2 0 F
0 0 0 0 D G 0 0 0 0 B 0 D G 0 0
A 0 0 0 0 0 E 0 A 0 0 0 0 0 E 0
【数据范围】
1 ≤ M ≤ 30
1 ≤ N ≤ 30
分析
这道题分析才是最重要的,老实说我看了很多找了题解,但是都只看了一眼我理解不了的代码就自己想通了。。
第一小问很巧妙,第一反应是暴搜,应该是不得行的,所以找个综合点的算法,作为一个最小值,还在棋盘上,结合青铜莲花池[1],应该可以想到这是一个隐式图搜索,但是因为仅仅是求增加的莲花数,所以这个是一个带权的最短路,用SPFA或者DIJ就可以做出来了,代码实现不难
然后我们看第二小题,我为了调试用fa映射输出了一个路径,然后改了改就得到了第二题的答案,然后来看第三小题
这个方案数应该是一个dp啊,可是这里用两个约束条件,一个是莲花增加数,一个是最短路径
其实我纠结了很久,但是没有想到其他的方法,所以直接写这个来之不易的思路:为了使得得到正确的父亲节点,我们进行两个标记,一个是莲花数,一个是在最少莲花数的最短路径,用两个二维数组开出来,实现见代码
有了这两个数组,因为这是一个DAG图,就可以从后往前一个记忆化dp,而且第二个数组还把第二小题直接做出来了。。
代码
#include<map>
#include<cmath>
#include<queue>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=35,inf=maxn*maxn*10;
typedef long long LL;
int a[maxn][maxn],dist[maxn][maxn],mind[maxn][maxn];
LL dp[maxn][maxn];
int ans1,ans2,ans3;
int n,m;
int dx[]={1,1,-1,-1,2,2,-2,-2};
int dy[]={2,-2,2,-2,1,-1,1,-1};
struct data
{
int x,y;
friend bool operator<(data a,data b)
{
return a.x!=b.x?a.x<b.x:a.y<b.y;//之前!=写成了==。。。有点不高兴
}
}u,v;
map<data,data>fa;
void Init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
if(a[i][j]==3)
u=(data){i,j};
if(a[i][j]==4)
v=(data){i,j};
}
}
}
bool inq[maxn][maxn];
void outpath(data x)
{
if(fa[x].x!=x.x||fa[x].y!=x.y)
outpath(fa[x]);
cout<<x.x<<" "<<x.y<<endl;
}
int get_step(data x)
{
if(fa[x].x!=x.x||fa[x].y!=x.y)
{
return get_step(fa[x])+1;
}
return 0;
}
void solve1and2()
{
queue<data>q;
q.push(u);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
dist[i][j]=inf;
mind[i][j]=inf;
}
}
dist[u.x][u.y]=0;
mind[u.x][u.y]=0;
fa[u]=u;
while(!q.empty())
{
data i=q.front();q.pop();
inq[i.x][i.y]=0;
for(int k=0;k<8;k++)
{
int nx=i.x+dx[k],ny=i.y+dy[k];
if(nx<1||ny<1||nx>n||ny>m)continue;
if(a[nx][ny]==2)continue;
if(a[nx][ny]==0)
{
if(dist[i.x][i.y]+1==dist[nx][ny])
mind[nx][ny]=min(mind[i.x][i.y]+1,mind[nx][ny]);
if(dist[i.x][i.y]+1<dist[nx][ny])
{
dist[nx][ny]=dist[i.x][i.y]+1;
fa[(data){nx,ny}]=(data){i.x,i.y};
mind[nx][ny]=mind[i.x][i.y]+1;
if(inq[nx][ny])continue;
inq[nx][ny]=1;
q.push((data){nx,ny});
}
}
else
{
if(dist[i.x][i.y]==dist[nx][ny])
mind[nx][ny]=min(mind[i.x][i.y]+1,mind[nx][ny]);
if(dist[i.x][i.y]<dist[nx][ny])
{
fa[(data){nx,ny}]=(data){i.x,i.y};
dist[nx][ny]=dist[i.x][i.y];
mind[nx][ny]=mind[i.x][i.y]+1;
if(inq[nx][ny])continue;
inq[nx][ny]=1;
q.push((data){nx,ny});
}
}
}
}
ans1=dist[v.x][v.y];
ans2=mind[v.x][v.y];
if(ans1>=inf)
ans1=-1;
cout<<ans1<<endl;
if(ans1!=-1)
cout<<ans2<<endl;
}
LL f(int x,int y)
{
if(dp[x][y]!=-1)
return dp[x][y];
if(x==u.x&&y==u.y)
return 1;
LL ret=0;
for(int k=0;k<8;k++)
{
int nx=x+dx[k],ny=y+dy[k];
if(nx<1||ny<1||nx>n||ny>m)continue;
if(a[nx][ny]==2)continue;
if(a[x][y]==0)
{
if(mind[nx][ny]+1==mind[x][y]&&dist[nx][ny]+1==dist[x][y])
ret+=f(nx,ny);
}
else
{
if(mind[nx][ny]+1==mind[x][y]&&dist[nx][ny]==dist[x][y])
ret+=f(nx,ny);
}
}
return dp[x][y]=ret;
}
void Debug()
{
for(int i=1;i<=4;i++)
{
for(int j=1;j<=4;j++)
{
cout<<dist[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
for(int i=1;i<=4;i++)
{
for(int j=1;j<=4;j++)
{
cout<<mind[i][j]<<" ";
}
cout<<endl;
}
}
void solve3()
{
memset(dp,-1,sizeof(dp));
cout<<f(v.x,v.y);
}
int main()
{
//freopen("in.txt","r",stdin);
Init();
solve1and2();
//Debug();
if(ans1!=-1)
solve3();
return 0;
}
注意的问题
1、关于映射这个东西,肯定要重载<运算符的,这里有时候出了问题很惨的,我为了第二小问的第一种解法调试了20分钟才发现的,以后结构体自定义符号运算要验证哦
2、注意这个dp的判断条件,能够走过去是判定新节点,究竟用没用新的莲花是看本身结点
3、方案数开LL
说明:为了fa的实现,特意留下来了这些没用的代码
关于BFS
BFS的输出路径用fa数组,fa数组一般的用数组,结构体或者数太大了就用map,记住重载运算符’<’
关于路径条数
一般针对DAG图,就是一个记忆化dp就可以做出来的,每一步的选择可以看是不是刚好由准备选择的那一步推出来的