题目描述
难度分:1300
输入T(≤104)表示T组数据。所有数据的n之和≤105,m之和≤105。
每组数据输入n(2≤n≤105)和长为n的严格递增数组a(0≤a[i]≤109)。
然后输入m(1≤m≤105)和m个询问,每个询问输入两个数x和y,范围[1,n]且x≠y。
数轴上有n个点,点i的坐标为a[i]。有两种移动方式:
-
从点i移动到离它最近的点,花费是1。保证每个点只有唯一的离它最近的点。
-
从点i移动到点j,花费是|a[i]−a[j]|。
对于每个询问,输出从点x移动到点y的最小花费。
输入样例
1
5
0 8 12 15 20
5
1 4
1 5
3 4
3 2
5 1
输出样例
3
8
1
4
14
算法
前缀和+后缀和
预处理出l2r数组和r2l数组,l2r[i]表示i走到i+1所需的代价,这时候需要检查i和左右邻居的距离哪个小,如果i与i+1的距离更小,那l2r[i]=1,否则l2r[i]=a[i+1]−a[i]。r2l数组也是同理,只是r2l[i]表示i走到i−1所需的代价。
然后对l2r求后缀和suf,对r2l求前缀和pre就可以O(1)计算每个询问了。如果x<y,则答案为suf[x]−suf[y],否则答案为pre[x]−pre[y]。
复杂度分析
时间复杂度
求l2r、r2l、pre和suf都是线性的时间复杂度,为O(n)。有了前后缀和数组pre和suf,每个query就可以O(1)求了。因此,整个算法的时间复杂度为O(n+m)。
空间复杂度
开辟了四个线性空间的数组l2r、r2l、pre和suf,额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
int a[N], l2r[N], r2l[N], pre[N], suf[N];
int main() {
int t;
scanf("%d", &t);
while(t--) {
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
l2r[1] = 1;
for(int i = 2; i < n; i++) {
if(a[i + 1] - a[i] < a[i] - a[i - 1]) {
l2r[i] = 1;
}else {
l2r[i] = a[i + 1] - a[i];
}
}
for(int i = n - 1; i >= 1; i--) {
suf[i] = suf[i + 1] + l2r[i];
}
r2l[n] = 1;
for(int i = n - 1; i >= 1; i--) {
if(a[i] - a[i - 1] < a[i + 1] - a[i]) {
r2l[i] = 1;
}else {
r2l[i] = a[i] - a[i - 1];
}
}
for(int i = 2; i <= n; i++) {
pre[i] = pre[i - 1] + r2l[i];
}
int m;
scanf("%d", &m);
while(m--) {
int x, y;
scanf("%d%d", &x, &y);
if(x < y) {
printf("%d\n", suf[x] - suf[y]);
}else {
printf("%d\n", pre[x] - pre[y]);
}
}
}
return 0;
}