题目

题目描述:
codeJan 非常喜欢旅行。现在有 n 个城市排在一条线上,并且 codeJan 的位置不和任何一个城市的位置重叠。
codeJan 想要游览 m 个城市,同时因为时间是不断变化的,游览一个城市多次也是允许的,但是不能永远待在一个城市,否则那样太无聊了。
给出这些城市的位置,codeJan 想要知道游览 m 个城市至少需要走多少米?

输入描述:
第一行是一个T≤20代表测试组数。
每组第一行是三个正整数n,m,p,分别代表城市数量、codeJan想要浏览的城市数量和codeJan当前的位置(单位为米)。
第二行包含n个正整数pos[i]表示第i个城市的位置,单位为米。
输入保证pos[i]<pos[i+1](i∈[1,n−1]),并且p ≠ pos[i](i∈[1,n])。

输出描述:
对于每组输入数据输出一个正整数表示 codeJan 至少需要走的距离。


解析

首先要看懂题目
  • 题目说codeJan要在城市之间移动m次,最后要知道至少走多少米。
  • 但是在看了样例之后,我们可以知道,移动到已经到达过的城市,也算一次移动

思路整理(一般情况,特殊情况未判断):
  1. 这道题在我们对题目理解了之后呢,我们可以明白,我们最后肯定是在一个区间内往返运动的
    举个栗子就是:你现在的m超级大,为了减小路程,我们肯定会选择走短路,那么我们就会在最短的路上反复的走。
  2. 那么这道题的简单思路就来了:我们只要求出我们在每一个区间内进行往返运动所要花的路程,然后在这里面选出最小值就好了

遍历操作:
  1. 既然我们要求出在每个区间往返运动所要花的路程,我们的计算公式就是:路程 = 到达往返点的路程 + 往返次数 x 往返区间长度
    (我们设起点左边的第一个点为near,为什么要有这个变量,我们往下看就知道了)
  2. 那么我们就需要以下几个元素(我们假设在i~i + 1之间往返):
    int move = p - pos[i];
    //从起点移动到往返点所需要的距离
    int circle = m - (near - i + 1);
    //到达了往返点之后,还需要的往返次数
    
    (1)往返区间在起点的前面的情况
    int move = pos[i] - p;
    int circle = m - (i - near);
    
    (2)往返区间在起点的后面的情况
  3. 然后按我们的公式套进去就是:
    ans = min(ans,  move + circle * (pos[i + 1] - pos[i]));
    

特殊情况判断:
  1. 单纯靠上面这样无法得到全部的情况:我们这样就是从起点单纯的往一边走,而放弃了另一边的所有点
  2. 那我们再来举个栗子:我们现在有四个点(2,5,10,15)。而我们的起点位置是在14。
    我们知道,假如m够大,在2~5之间往返绝对很划算对不对。但是我们的起点并不是在小镇,而是在小镇中间
    如果我们先去一趟15的位置,再回到14来,只要走两步,是不是很划得来!
  3. 所以我们要考虑到另一边的点会不会让我们的步数更加划得来
  4. 那么你可能就会问了:既然可以走一步,那走两步呢?往反方向走两步可能更划得来呢?
  5. 结论是:不可能!因为假如往反方向走两步更划得来,说明他们之间距离很小,那我们还有啥必要走正方向呢?直接走反方向不就行了?

特判操作:
  1. 所以根据我们上面说的,我们对反方向走一步,进行特判。
  2. 我们的计算公式就是:总路程 = 反方向走到第一个村庄的距离 x 2 + 到达往返点的路程 + (往返次数 - 1)x 往返区间长度
    if (circle && near + 1 <= n)//约束条件,第一个点和最后一个点
            ans = min(ans, 2 * (pos[near + 1] - p) + move + (circle - 1) * (pos[i + 1] - pos[i]));
    //这是往返区间在左边的情况,所以往右找
    if (circle && near >= 1)
            ans = min(ans, 2 * (p - pos[near]) + move + (circle - 1) * (pos[i + 1] - pos[i]));
    //这是往返区间在右边的情况,所以往左找
    
    //if判断条件特判左右端点,因为端点没有可以回头的点嘛
    为啥要乘2呢?因为是去了第一个点又要回到起始点;为啥往返要-1呢?因为我们已经去了一次反方向的点。

bb了这么多,总算可以打代码了:
  1. 我们先存好变量,要记得把起<stron>(现在大家应该懂了为什么要有这个变量,为了找到反方向的第一个点呗)。 </stron>
  2. 存好了之后呢,我们就按照上面的操作,对每个点把情况求出来。
  3. 看代码吧~


AC代码

#include <iostream>
using namespace std;
typedef long long ll;
#define IOS ios::sync_with_stdio(false);
//代码预处理区

const ll INF = 0x3f3f3f3f3f3f3f3f;
const int MAX = 1e5 + 7;
ll pos[MAX];
//全局变量区

int main() {
    IOS;
    int T; cin >> T;
    while (T--) {
        int n, m, p; cin >> n >> m >> p;
        int near = 0;
        for (int i = 1; i <= n; i++) {
            cin >> pos[i];
            if (pos[i] < p) near = i;
        }
        //输入并标记起点左边的城市
        ll ans = INF;
        for (int i = 1; i < n; i++)
            if (i <= near) {
                if (m < near - i + 1) continue;
                int move = p - pos[i];
                //移动到往返点所需要的距离
                int circle = m - (near - i + 1);
                //到达了往返点之后,还需要的往返次数
                ans = min(ans,  move + circle * (pos[i + 1] - pos[i]));
                if (circle && near + 1 <= n)
                    ans = min(ans, 2 * (pos[near + 1] - p) + move + (circle - 1) * (pos[i + 1] - pos[i]));
                //判断是否越界,进行向前进反方向移动的特判。
            }
            else {
                if (m < i - near) continue;
                int move = pos[i] - p;
                int circle = m - (i - near);
                ans = min(ans, move + circle * (pos[i + 1] - pos[i]));
                if (circle && near >= 1)
                    ans = min(ans, 2 * (p - pos[near]) + move + (circle - 1) * (pos[i + 1] - pos[i]));
            }
        cout << ans << endl;
    }
    return 0;
}
//函数区