呐~原题链接
题目描述
张经理的公司的办公室长达100000米,从最左端开始每间隔1米都有一个工位(从第1米开始有工位),位于第i米的工位称为i号工位,且这些工位都在一条水平线上。他有n个员工,每个员工分别位于xi号工位上(不同员工可能位于同一个工位)。
现在张经理想把员工聚集在某两个工位上,他有q套方案(每套方案包含两个工位号,两个工位号可能相同),他想知道对于每套方案,所有员工都到达两个工位中的某一个所需走的最短路径之和是多少。
输入描述:
第一行输入两个正整数n, q
第二行输入n个正整数xi,分别代表第i个员工的工位
之后q行每行输入两个整数a,b,代表该套方案要求的两个集合位置
(1<=n,q,xi,a,b<=105)(1<=n,q,xi,a,b<=10^5)(1<=n,q,xi,a,b<=105)
输出描述:
对于每套方案,输出一个整数代表答案,每个答案独占一行。
输入输出示例
题解一:遍历加
这道题简单的思路是遍历,对于每一个员工的工位,计算他到那两个工位号的距离的较小值加到最终结果上去,时间复杂度为O(n*n)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = (int)2e4 + 1000;
int min_p = INT_MAX;//int 型的最大值
int max_p = INT_MIN;
const int mod = (int)1e9 + 7;
const int inf = 0x3f3f3f3f; //初始化无穷大
int a[100005];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; ++i)
cin >> a[i];
while (q)
{
int x, y;
int ans1 = 0, ans2 = 0;
cin >> x >> y;
for (int i = 1; i <= n; ++i)
{
int c = abs(a[i] - x);
int d = abs(a[i] - y);
ans1 = c < d ? c : d;
ans2 += ans1;
}
cout << ans2 << endl;
--q;
}
return 0;
}
题解二:二分+前缀和
这道题按上面的做法,对时间复杂度要求严格点就过不了了,下面来时间复杂度为O(n*logn)的
思路就是,先对工位非降序排列,然后假设最后要集合的工位x<=y,那么排好序的工位中,位于(x+y)/2位置之前的到x的距离近,位于(x+y)/2位置之后的到y的距离近。
所以我们只需要将数轴分为四段,即(1—x)(x—mid)(mid—y)(y----n)
找到x,mid,y的位置,然后计算距离差
第一个不大于x的数值的下标p1
第一个大于等于y的数值的下标p2
第一个不大于mid的数值的下标p3
(1—x):x*p1-sum[p1]
(x—mid):sum[p3]-sum[p1]-(p3-p1)*x
(mid—y):(p2-p3-1)*y-(sum[p2-1]-sum[p3])
(y----n):sun[n]-sum[p2-1]-(n-p2+1)*y
为什么会用到前缀和呢,因为比如我们要算1 2 3 4 5到6的距离差,我们可以(6-1)+(6-2)+(6-3)+(6-4)+(6-5)=6*5-(1+2+3+4+5)
即5个6-前5项的和
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[100005];
ll sum[100005] = { 0 };
int main() {
ios::sync_with_stdio(false);
int n, q;//n位员工,q种方案
cin >> n >> q;
for (int i = 1; i <= n; i++) cin >> a[i];//输入员工坐标
sort(a + 1, a + n + 1);//升序排列
for (int i = 1; i <= n; i++)sum[i] = sum[i - 1] + a[i];//求前缀和
while (q--)
{
ll x, y;//两个工位
cin >> x >> y;
if (x > y)swap(x, y);//假定就让x比y小
ll mid = (x + y) / 2;//mid左边的离x近,右边的离y近
ll res = 0;//所求
int p1 = upper_bound(a + 1, a + n + 1, x) - a;//找到第一个大于x的数值的下标
p1--;//第一个不大于x的数值的下标
int p2 = lower_bound(a + 1, a + n + 1, y) - a;//找到第一个大于等于y的数值的下标
int p3 = upper_bound(a + 1, a + n + 1, mid) - a;//找到第一个大于mid的数值的下标
p3--;//第一个不大于mid的数值的下标
//所求mid已左到x近,已右到y近
res = x * p1 - sum[p1] + (sum[p3] - sum[p1]) - (p3 - p1) * x + (p2 - p3 - 1) * y - sum[p2 - 1] + sum[p3] + sum[n] - sum[p2 - 1] - (n - p2 + 1) * y;
cout << res << endl;
}
return 0;
}