【USACO】青铜莲花池[2]

本文介绍了USACO青铜莲花池问题,探讨如何帮助奶牛贝茜通过跳跃莲花到达目标,解决添加最少莲花数量、最短跳跃步数及路径条数的问题。通过分析,提出使用带权最短路算法和记忆化DP来求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

搜索到这篇文章的朋友,那么很巧了,我们多半是一个学校的,为什么呢?因为这道题叫白银莲花池。。

题目

【问题描述】

  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就可以做出来的,每一步的选择可以看是不是刚好由准备选择的那一步推出来的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值