题目
题目描述: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次,最后要知道至少走多少米。
- 但是在看了样例之后,我们可以知道,移动到已经到达过的城市,也算一次移动。
思路整理(一般情况,特殊情况未判断):
- 这道题在我们对题目理解了之后呢,我们可以明白,我们最后肯定是在一个区间内往返运动的。
举个栗子就是:你现在的m超级大,为了减小路程,我们肯定会选择走短路,那么我们就会在最短的路上反复的走。 - 那么这道题的简单思路就来了:我们只要求出我们在每一个区间内进行往返运动所要花的路程,然后在这里面选出最小值就好了。
遍历操作:
- 既然我们要求出在每个区间往返运动所要花的路程,我们的计算公式就是:路程 = 到达往返点的路程 + 往返次数 x 往返区间长度。
(我们设起点左边的第一个点为near,为什么要有这个变量,我们往下看就知道了) - 那么我们就需要以下几个元素(我们假设在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)往返区间在起点的后面的情况
- 然后按我们的公式套进去就是:
ans = min(ans, move + circle * (pos[i + 1] - pos[i]));
特殊情况判断:
- 单纯靠上面这样无法得到全部的情况:我们这样就是从起点单纯的往一边走,而放弃了另一边的所有点。
- 那我们再来举个栗子:我们现在有四个点(2,5,10,15)。而我们的起点位置是在14。
我们知道,假如m够大,在2~5之间往返绝对很划算对不对。但是我们的起点并不是在小镇,而是在小镇中间。
如果我们先去一趟15的位置,再回到14来,只要走两步,是不是很划得来! - 所以我们要考虑到另一边的点会不会让我们的步数更加划得来。
- 那么你可能就会问了:既然可以走一步,那走两步呢?往反方向走两步可能更划得来呢?
- 结论是:不可能!因为假如往反方向走两步更划得来,说明他们之间距离很小,那我们还有啥必要走正方向呢?直接走反方向不就行了?
特判操作:
- 所以根据我们上面说的,我们对反方向走一步,进行特判。
- 我们的计算公式就是:总路程 = 反方向走到第一个村庄的距离 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了这么多,总算可以打代码了:
- 我们先存好变量,要记得把起<stron>(现在大家应该懂了为什么要有这个变量,为了找到反方向的第一个点呗)。 </stron>
- 存好了之后呢,我们就按照上面的操作,对每个点把情况求出来。
- 看代码吧~
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; } //函数区