【动态规划】游艇租赁问题

题目

长江俱乐部在长江设置了n个游艇出租站1,2,…n,游客可在这些游艇出租站租用游艇,并在下游的任何一个游艇出租站归还游艇。游艇出租站i到游艇出租站j之间的租金为r(i,j), 现在要求游艇从出租站1到出租站n所需要的最少租金。

问题分析

在中间不同的出租站停靠就会有不同的租金,那么我们就看在哪些出租站停靠的时候租金最少。

算法

这部分有参考《趣学算法》,也有很多自己的理解和思考,如有bug,欢迎批评指点。(鞠躬)

动态规划

这个问题也是具有最优子结构,就是整个问题的最优解会包括子问题的最优解。以下是证明过程:
s为从出租站i经中间停靠点k到出租站j的最少租金,s1为从i到k的租金,s2为从k到j的租金,则s=s1+s2。假设s1不是i到j的最少租金,那么一定i到j一定存在最少租金t1<s1,采取t1对应的停靠方法,从i到j的租金为t1+s2<s,这与s是i到j的最少租金矛盾,所以s1为i到k的最少租金,同理可证s2为k到j的最少租金。

算法核心

这个问题就是先求出子问题也就是两点间距离(这里是指序号的临近程度)较小的解,然后用这些已知解来继续求两点间距离更大的子问题的解,最后得到从出租站1到出租站n的最优解,也就是最少租金。最重要的当然是由子问题的最优解得到更大子问题最优解的递推公式咯。
r[][]:两个出租站之间的租金;
m[][]:两个出租站之间的最少租金;
s[][]:两个出租站取得最少租金的中间停靠点;
i:前一个出租站;
j:后一个出租站;
if j==i+1,m[i][j]=r[i][j];
if j>i+1,m[i][j]=min{m[i][k]+m[k][j],m[i][]j},k为i和j中间的出租站;
这个递推公式还是比较好理解的,就是如果中间停一次能减少租金,那就停一次,否则就不停,至于具体停在哪个出租站,循环遍历中间出租站时记录取得最小值的出租站序号即可。

算法流程

首先输入r[][],将m[i][j]初始化为r[i][j],就是不停靠的情况。然后设循环,注意这里是按照两出租站间的距离来设的,就是先计算距离为2的(距离为1的不需要停靠,所以不用计算)的,在计算距离为3的…直到最后计算到距离为n-1的,也就是从出租站1到出租站n。距离确定后,按照序号顺序,比如说距离为3,先计算出租站1到出租站4的最少租金,在两点间的出租站依次停靠,就是取k=2,3,之后按之前的递推公式m[1][4]取到最小值,然后再计算出租站2到出租站5的最少租金…直至出租站n-3到出租站n,这里我是判断i+d(距离)与n大小关系来确定是否需要设停靠站的这个循环,也可以直接在循环变量的取值范围中体现。这样三重循环结束之后就可以得到m[1][n]了,这就是从出租站1到出租站n的最少租金。
要得到停靠路线,就写一个output(int i,int j)递归函数,如果s[i][j]==0,就说明这两点之间租金最少的时候不需要停靠,直接输出j;否则就得到停靠点s[i][j],然后输出i到s[i][j]的路线和s[i][j]到j的路线即可。

代码实现

#include<iostream>
#include<cstring>
using namespace std;
const int maxn=105;

int r[maxn][maxn];//两个出租站之间的租金
int m[maxn][maxn];//两个出租站之间的最少租金
int s[maxn][maxn];//两个出租站取得最少租金的中间停靠点
int n;//所有出租站的数目

void min_rent()//求出任意两个出租站之间的最少租金
{
    int i,j,k,d;
    for(d=2; d<=n-1; ++d) //两点间距离,到本身租金为0,距离为1不用计算,距离>=2才计算
        for(i=1; i<n-1; ++i) //计算每一个出租站到距离为d的出租站的最少租金,最后两个停靠点不用计算
            if(i+d<=n)//存在距离为d的出租站
            {
                k=i+d;
                for(j=i+1; j<i+d; ++j) //遍历使两者间的每一个出租站,看其是否能成为停靠点(停靠站的循环)
                {
                    int temp=m[i][j]+m[j][k];
                    //cout<<"temp:"<<temp<<endl;
                    if(temp<m[i][k])
                    {
                        m[i][k]=temp;
                        s[i][k]=j;//记录中间停靠点
                    }
                }
            }
}

void output(int i,int j)
{
    if(s[i][j]==0)//没有中间停靠点
    {
        cout<<" "<<j;
        return ;
    }
    else
    {
        output(i,s[i][j]);
        output(s[i][j],j);
    }
}

int main()
{
    memset(r,0,sizeof(r));
    memset(m,0,sizeof(m));
    memset(s,0,sizeof(s));
    cout<<"请输入出租站数目:";
    cin>>n;
    int i,j;
    cout<<"请输入两出租站间的租金:";
    for(i=1; i<=n; ++i) //将两点间租金存储在数组右上那半部分
        for(j=i+1; j<=n; ++j)
        {
            cin>>r[i][j];
            m[i][j]=r[i][j];
        }
    min_rent();
    cout<<"最少租金为:"<<m[1][n];
    cout<<"选择的路线为:";
    cout<<"1";
    output(1,n);
    return 0;
}
  • 6
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值