有趣的隐式图模型——USACO CONTEST FEB07 白银莲花池

本文通过一个USACO竞赛的问题,介绍如何利用隐式图模型解决棋盘上最短路径问题,同时考虑最小代价和路径条数。文章详细分析了解题思路,包括Dijkstra/SPFA算法的应用,以及如何在求解过程中同时获取最小代价和最小步数的最优路径条数。
摘要由CSDN通过智能技术生成

【问题描述】

  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.先把最优情况下的方案数搁在一边,看看怎么求最小距离(步数)。
既然是隐式图的模型,那么肯定要用Dijkstra/SPFA求解啦。那么写着写着问题就来了,如何让最小的铺路代价优先于步数呢?一开始想设置状态数组dist[i][j][g]表示从起点走到(i,j),铺路代价为g的最少步数,但是发现无法保证代价优先,而且思路很混乱。
因为每个格子有两个需要求的最优值,明智的选择就是开两个数组dist[i][j]->最少步数,cost[i][j]->最小代价,每从节点(x,y)搜索到一个新的点(tx,ty)的时候松弛操作的思路如下:

  1. if(cost[x][y]+cost((x,y)->(tx,ty)) 小于 cost[tx][ty])那么不论步数如何,都应该从(x,y)走到(tx,ty)。
  2. if(cost[x][y]+cost((x,y)->(tx,ty))==cost[tx][ty]) 那么还要判断步数是否更少 if(dist[x][y]+1 小于 dist[tx][ty]) 才从(x,y)走到(tx,ty)

那么算完之后的dist[ex][ey],cost[ex][ey]就是最小步数和最小代价了

2.如何计算最优路径条数呢?
第一种想法是把在最优路径上的边连成一张DAG图,再在图上进行BFS+动态规划,但是这里已经写了这么长一段代码了能不能好好利用一下呢?
我们发现算最优路的松弛操作时,不就可以顺带把方案数求出来了吗。我们设f[i][j]表示从起点到(i,j)的最优路径条数,思路如下:

  1. 如果从(x,y)->(tx,ty)更优,那么f[tx][ty]=f[x][y];
  2. 如果从(x,y)->(tx,ty)一样优秀,那么f[tx][ty]+=f[x][y];

这两个步骤可以合在一段最短路的代码里面,然后输出就可以了。


#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=35;
const int inf=16843009;
int w[maxn][maxn],dist[maxn][maxn],inq[maxn][maxn],cost[maxn][maxn],n,m,sx,sy,ex,ey;
LL f[maxn][maxn];
int dx[]={-1,-2,-2,-1,1,2,2,1},dy[]={2,1,-1,-2,-2,-1,1,2};
struct data
{
    int x,y;
};
void SPFA()
{
    queue<data>q;
    q.push((data){sx,sy});
    memset(dist,1,sizeof(dist));
    memset(cost,1,sizeof(cost));
    dist[sx][sy]=0;
    cost[sx][sy]=0;
    inq[sx][sy]=1;
    f[sx][sy]=1;
    data t,tt;
    int ww;
    while(!q.empty())
    {
        t=q.front();q.pop();
        if(t.x==ex && t.y==ey)
        {
            continue;
        } 
        inq[t.x][t.y]=0;
        for(int x=0;x<8;x++)
        {
            ww=0;
            tt=(data){t.x+dx[x],t.y+dy[x]};
            if(tt.x<1 || tt.x>n || tt.y<1 || tt.y>m) continue;
            if(w[tt.x][tt.y]==2) continue;
            if(w[tt.x][tt.y]==0) ww=1;

            //松弛操作: 
            if(cost[t.x][t.y]+ww<cost[tt.x][tt.y])//代价较小则必选(不考虑步数大小) 
            {
                cost[tt.x][tt.y]=cost[t.x][t.y]+ww;
                dist[tt.x][tt.y]=dist[t.x][t.y]+1;
                f[tt.x][tt.y]=f[t.x][t.y];
                if(inq[tt.x][tt.y]) continue;
                inq[tt.x][tt.y]=1;
                q.push(tt); 

            }
            else if(cost[t.x][t.y]+ww==cost[tt.x][tt.y])//代价相等
            {
                if(dist[t.x][t.y]+1<dist[tt.x][tt.y])//步数较小 
                {
                    dist[tt.x][tt.y]=dist[t.x][t.y];
                    f[tt.x][tt.y]=f[t.x][t.y];
                    if(inq[tt.x][tt.y]) continue;
                    inq[tt.x][tt.y]=1;
                    q.push(tt);
                }
                else if(dist[t.x][t.y]+1==dist[tt.x][tt.y])//步数也相等则方案数相加
                {
                    f[tt.x][tt.y]+=f[t.x][t.y];
                }
            }
        }
    }
    if(dist[ex][ey]==inf) 
    {
        printf("-1\n");
        return;
    }
    printf("%d\n%d\n",cost[ex][ey],dist[ex][ey]);
    cout<<f[ex][ey];
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        scanf("%d",&w[i][j]);
        if(w[i][j]==3) sx=i,sy=j;
        else if(w[i][j]==4) ex=i,ey=j;
    }
    SPFA();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值