• 【最短路算法例题-升降梯上】-C++


    描述
    启了升降梯的动力之后,探险队员们进入了升降梯运行的那条竖直的隧道,映入眼帘的是一条直通塔顶的轨道、一辆停在轨道底部的电梯、和电梯内一杆控制电梯升降的巨大手柄。
    Nescafe之塔一共有N层,升降梯在每层都有一个停靠点。手柄有M个控制槽,第i个控制槽旁边标着一个数C_i,满足C1<C2<C3<...<CM。如果Ci>0,表示手柄扳动到该槽时,电梯将上升Ci层;如果Ci<0,表示手柄扳动到该槽时,电梯将下降 −Ci层;并且一定存在一个Ci=0,手柄最初就位于此槽中。注意升降梯只能在1~N层间移动,因此扳动到使升降梯移动到1层以下、N层以上的控制槽是不允许的。
    电梯每移动一层,需要花费2秒钟时间,而手柄从一个控制槽扳到相邻的槽,需要花费1秒钟时间。探险队员现在在1层,并且想尽快到达N层,他们想知道从1层到N层至少需要多长时间?
    输入
    第一行两个正整数N、M。
    第二行M个整数C_1、C_2、...、C_M。
    输出
    输出一个整数表示答案,即至少需要多长时间。若不可能到达输出-1。
    输入样例 1 
    6 3
    -1 0 2
    输出样例 1
    19
    提示
    数据范围:
    1 <= n <= 1000
    1 <= m <= 20
    
    样例分析:
    
    手柄从第二个槽扳到第三个槽(0扳到2),用时1秒,电梯上升到3层,用时4秒。
    手柄在第三个槽不动,电梯再上升到5层,用时4秒。
    手柄扳动到第一个槽(2扳到-1),用时2秒,电梯下降到4层,用时2秒。
    手柄扳动到第三个槽(-1扳倒2),用时2秒,电梯上升到6层,用时4秒。
    总用时为(1+4)+4+(2+2)+(2+4)=19秒。
    
    
    

    这道题说实话,我刚做的时候看不出用什么办法来做。所以我选择了:
    广搜!
    每个状态分别为:当前楼层,花费时间,拉杆目前位置。
    但是搜索一波出来发现:
    只能过两组!
    为什么呢?
    很明显,光凭借广搜,我们无法一次得出最优的答案。所以我们就要考虑使用其他方法。
    根据机房大佬和老师的帮助 ,这道题的正解是最短路!

    那么怎么构图?

    这就是这道题的核心问题。再次经过冥思苦想(确信),我们在二维的状态下,可以用dst[i][j]表示从拉杆位置在j时上到第i层所需要的时间
    但是二维的状态我们难以形成最短路。
    那怎么办?
    二维转一维!
    直接写一个convert函数,把一个数对转换成一个编号。那么:

    int convert(int x,int y)
    {
        return (x-1)*m+y;
    }
    

    接下来就是构图。
    构图我的方法比较麻烦,用到了三层循环。大概代码如下:

    for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                for(int k=1;k<=m;k++)
                {
                	int u=convert(i,j),v=convert(i+c[k],k),w=abs(j-k)+abs(2*c[k]);
                	g[u].push_back(node(v,w));
                }
            }
        }
    

    因为我用到的是迪杰斯特拉算法(不了解的看看这篇博客再继续看下去哈),所以用到的是二维结构体vector来存图。前置定义如下

    struct node
    {
        int v,w;
        node(){}
        node(int vv,int ww)
        {
            v=vv,w=ww;
        }
    };
    vector<node> g[200010];
    

    然后就可以用最短路模板来AC这道题目了,但是要注意的是,这道题的点数不是n,而是n*m!因为我们是把二维转成的一维,所以点数要乘上可能到达这个点的拉杆槽的数量!
    最短路函数主体:

    void dij(int p)
    {
    	for(int i=1;i<=200010;i++)
    	{
    		dst[i]=INF;
    		s[i]=0;
    	}
        s[p]=1;
        dst[p]=0;
        int lasti=p;
        for(int k=1;k<n*m;k++)
        {
            for(int j=0;j<g[lasti].size();j++)
            {
                int v=g[lasti][j].v,w=g[lasti][j].w;
                if(!s[v]&&dst[v]>w+dst[lasti])
                {
                    dst[v]=w+dst[lasti];
                }
            }
            int min_i=INF,min_dst=INF;
            for(int i=1;i<=n*m;i++)
            {
                if(!s[i])
                {
                    if(dst[i]<min_dst)
                    {
                        min_dst=dst[i];
                        min_i=i;
                    }
                }
            }
            lasti=min_i;
            s[min_i]=1;
        }
    }
    

    最后一个问题:

    我们最后输出啥?

    最短路已经找出来,但是我们能直接输出dst[n]吗?
    很明显不行!!!
    我们的dst数组的每一个下标,对应的是一个数对,而不是一个点!
    所以,我们要从以convert(n,1)为下标的dst值遍历到以convert(n,m)为下标的dst值,最后输出最小值即可。一个循环就可以搞定。
    但是别忘了!题目中还有个输出"-1"的判断需求,需要注意的是,当我们无法到达第n楼的时候,对于任意一个小于m的i,dst[n] [convert(n,i)]的值是不会改变的!所以我们只需要在输出的时候判断一下ans是否有更新操作,没有就输出-1.
    这一步代码如下:

    int ans=INF;
        for(int i=1;i<=m;i++)
        {
        	int r=convert(n,i);
        	ans=min(ans,dst[r]);
    	}
    	if(ans!=INF)
    		cout<<ans<<endl;
    	else cout<<-1<<endl;
    

    这是为了判断题中的特殊情况。
    为了配合最终答案在 “无解” 的情况下dst[n]的任意值不会更新的特性,我们要在迪杰斯特拉的最外层循环的最后加上一句:

    if(min_i==INF)return;
    
    完整代码如下:
    #include<bits/stdc++.h>
    #define INF 0x3f3f3f3f
    using namespace std;
    int c[1000010],n,m;
    bool s[1000010];
    int convert(int x,int y)
    {
        return (x-1)*m+y;
    }
    int dst[200010];
    struct node
    {
        int v,w;
        node(){}
        node(int vv,int ww)
        {
            v=vv,w=ww;
        }
    };
    vector<node> g[200010];
    void dij(int p)
    {
    	for(int i=1;i<=200010;i++)
    	{
    		dst[i]=INF;
    		s[i]=0;
    	}
        s[p]=1;
        dst[p]=0;
        int lasti=p;
        for(int k=1;k<n*m;k++)
        {
            for(int j=0;j<g[lasti].size();j++)
            {
                int v=g[lasti][j].v,w=g[lasti][j].w;
                if(!s[v]&&dst[v]>w+dst[lasti])
                {
                    dst[v]=w+dst[lasti];
                }
            }
            int min_i=INF,min_dst=INF;
            for(int i=1;i<=n*m;i++)
            {
                if(!s[i])
                {
                    if(dst[i]<min_dst)
                    {
                        min_dst=dst[i];
                        min_i=i;
                    }
                }
            }
            if(min_i==INF)return;
            lasti=min_i;
            s[min_i]=1;
        }
    }
    
     
    int main()
    {
        cin>>n>>m;
        int now;
        for(int i=1;i<=m;i++)
        {
            cin>>c[i];
            if(c[i]==0)now=i;
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                for(int k=1;k<=m;k++)
                {
                	int u=convert(i,j),v=convert(i+c[k],k),w=abs(j-k)+abs(2*c[k]);
                	g[u].push_back(node(v,w));
                }
            }
        }
        dij(convert(1,now));
        int ans=INF;
        for(int i=1;i<=m;i++)
        {
        	int r=convert(n,i);
        	ans=min(ans,dst[r]);
    	}
    	if(ans!=INF)
    		cout<<ans<<endl;
    	else cout<<-1<<endl;
        return 0;
    }
    

    ov.

    个人博客地址: www.moyujiang.com 或 moyujiang.top
  • 相关阅读:
    二叉树之小球下落
    ACM规划型。。
    经典函数看待问题。。
    用putty连接ubuntu
    minheight最小高度的实现(兼容IE6、IE7、FF)(解决IE6不兼容minheight)
    elementui里面的form表单i验证input内容已经输入不为空了,但效验还是报错不能为空
    js中10位数的时间戳必须*1000才能格式化转换
    chrome已安装Vue Devtools在控制台却无显示
    elementui使用day.js格式化后端接口里的日期时间戳
    elementui分页更改后,需要再次调用获取列表数据函数
  • 原文地址:https://www.cnblogs.com/moyujiang/p/11167767.html
Copyright © 2020-2023  润新知