题目内容
给定一个长度为 n n n 的数组 a a a ,现在需要你从中选择 k k k个数,使得选择的 k k k 个数构成的 k × ( k − 1 ) 2 \frac{k\times (k-1)}{2} 2k×(k−1) 个数对的差的绝对值之和最小,请你输出选择的 k k k 个数,如果有多种答案,输出任意一种即可。
数据范围
- 3 ≤ n ≤ 3 ⋅ 1 0 5 3\leq n\leq 3\cdot 10^5 3≤n≤3⋅105
- − 1 0 8 ≤ a i ≤ 1 0 8 -10^8\leq a_i\leq 10^8 −108≤ai≤108
- 2 ≤ k ≤ n − 1 2\leq k\leq n-1 2≤k≤n−1
题解
这里我们先将 a a a 数组进行排序,后续所说的数组 a a a 也都是从小到大排序的。
有一个很直观的想法,即我们选择的 k k k 个数,在 a a a 数组中,一定是连续的 k k k 个数。
那么问题就转换为求所有窗口大小为 k k k 的数对的差值的绝对值之和,找出这个值最小的窗口。
我们称窗口大小为 k k k 的数对的差值的绝对值之和为窗口权值。
假设当前窗口为 a l a_l al 到 a r a_r ar ,窗口权值为 s u m sum sum
增加一个新的数
a
r
+
1
a_{r+1}
ar+1 ,添加的窗口权值为:
∑
i
=
l
r
a
r
+
1
−
a
i
=
(
r
−
l
+
1
)
×
a
r
+
1
−
∑
i
=
l
r
a
i
\sum\limits_{i=l}^r a_{r+1}-a_i=(r-l+1)\times a_{r+1}-\sum\limits_{i=l}^r a_i
i=l∑rar+1−ai=(r−l+1)×ar+1−i=l∑rai
删除一个窗口中的数
a
l
a_l
al ,减少的窗口权值为:
∑
i
=
l
+
1
r
+
1
a
i
−
a
l
=
(
∑
i
=
l
+
1
r
+
1
a
i
)
−
(
r
−
l
+
1
)
×
a
l
\sum\limits_{i=l+1}^{r+1}a_i-a_l=(\sum\limits_{i=l+1}^{r+1}a_i)-(r-l+1)\times a_l
i=l+1∑r+1ai−al=(i=l+1∑r+1ai)−(r−l+1)×al
所有只要维护一个区间所有值的和,以及窗口权值即可。
最后取 窗口权值最小 的窗口作为答案即可。
时间复杂度: O ( n log n ) O(n\log n) O(nlogn) ,瓶颈在于排序
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
void solve() {
int n;
cin >> n;
vector<pair<int, int>> p(n);
for (int i = 0; i < n; ++i) cin >> p[i].first, p[i].second = i;
int k;
cin >> k;
sort(p.begin(), p.end());
// 问题在于你需要求出这 k 个点互相距离之和
// 添加一个数后,将这个数对应的所有点对距离之和全部删除
// 添加一个数后,整个和添加了 (xr-xl) + (xr-xl+1) + ... + (xr-xr-1)
// 等价于 (r-l)*xr - (xl+xl+1+...+xr-1)
// 删除一个数后,整个和减少了 (xr-xl) + (xr-1-xl) + ... + (xl+1-xl)
// 等价于 (xl+1+xl+2+...+xr)-(r-l)*xl
ll min_sum = 9e18;
ll seg_sum = 0;
ll sum = 0;
pair<int, int> ans = make_pair(0, k - 1);
for (int r = 0, l = 0; r < n; ++r) {
seg_sum += 1ll * (r - l) * p[r].first - sum;
sum += p[r].first;
if (r >= k - 1) {
if (seg_sum < min_sum) {
min_sum = seg_sum;
ans = make_pair(r - k + 1, r);
}
sum -= p[l].first;
seg_sum -= sum - 1ll * (r - l) * p[l].first;
l += 1;
}
}
for (int i = ans.first; i <= ans.second; ++i) {
cout << p[i].second + 1 << " \n"[i == ans.second];
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
证明
选择的 k k k 个数,在 a a a 数组中,一定是连续的 k k k 个数。
即需要证明,对于任意一种非连续的 k k k 个数区间 A A A,必然存在一种连续区间 B B B , B B B 的窗口权值小于 A A A 的窗口权值
当前选择的一个连续区间是 a i , a i + 1 , ⋯ , a j − 1 , a j a_i,a_{i+1},\cdots,a_{j-1},a_j ai,ai+1,⋯,aj−1,aj ,满足 j − i + 1 = k j-i+1=k j−i+1=k
如果你认为非连续区间的窗口权值可能更小?
不妨将区间拆成三部分:
- ① ( a i , a i + 1 , ⋯ , a p − 1 ) (a_i,a_i+1,\cdots,a_{p-1}) (ai,ai+1,⋯,ap−1)
- ② ( a p ) (a_p) (ap)
- ③ ( a p + 1 , a p + 2 , ⋯ , a j ) (a_{p+1},a_{p+2},\cdots, a_j) (ap+1,ap+2,⋯,aj)
将 a p a_p ap 替换成 a i a_i ai 左边的数,或者 a j a_j aj 右边的数,
必然要么替换成 a i − 1 a_{i-1} ai−1 或者 a j + 1 a_{j+1} aj+1
因为如果替换成 a j + 1 a_{j+1} aj+1 右边的数,必然不如替换成 a j + 1 a_{j+1} aj+1,如果替换成 a i − 1 a_{i-1} ai−1 左边的数,必然不如替换成 a i − 1 a_{i-1} ai−1
因为这两个数分别是新的窗口的最左端点和最右端点了,窗口左端点一味地小,右端点一味地大,必然使得窗口权值变大。
所以来考虑替换成 a i − 1 a_{i-1} ai−1 或者 a j + 1 a_{j+1} aj+1 的情况
-
替换成 a i − 1 a_{i-1} ai−1
- 如果 ① 的数量小于等于 ③ 的数量,必然不如不替换
- 如果 ① 的数量大于 ③ 的数量,这个窗口权值和
(
a
i
−
1
,
a
i
,
⋯
a
j
−
2
,
a
j
−
1
)
(a_{i-1},a_i,\cdots\ a_{j-2},a_{j-1})
(ai−1,ai,⋯ aj−2,aj−1) 这个窗口权值相比呢?
- 相当于将 ( a i − 1 , a i , ⋯ a j − 2 , a j − 1 ) (a_{i-1},a_i,\cdots\ a_{j-2},a_{j-1}) (ai−1,ai,⋯ aj−2,aj−1) 这个窗口的 a p a_p ap 移动到 a j a_j aj ,在 ① 的数量大于 ③ 的数量 的条件下,必然不如不替换 ( a i − 1 , a i , ⋯ a j − 2 , a j − 1 ) (a_{i-1},a_i,\cdots\ a_{j-2},a_{j-1}) (ai−1,ai,⋯ aj−2,aj−1) 这个窗口
-
替换成 a j + 1 a_{j+1} aj+1
其实恰好是上述替换方案的逆过程- 如果 ① 的数量大于等于 ③ 的数量,必然不如不替换
- 如果 ① 的数量小于 ③ 的数量,这个窗口权值和
(
a
i
+
1
,
a
i
+
1
,
⋯
a
j
−
1
,
a
j
,
a
j
+
1
)
(a_{i+1},a_{i+1},\cdots\ a_{j-1},a_{j},a_{j+1})
(ai+1,ai+1,⋯ aj−1,aj,aj+1) 相比呢?
- 相当于将 ( a i + 1 , a i + 1 , ⋯ a j − 1 , a j , a j + 1 ) (a_{i+1},a_{i+1},\cdots\ a_{j-1},a_{j},a_{j+1}) (ai+1,ai+1,⋯ aj−1,aj,aj+1) 这个窗口的 a p a_p ap 移动到 a i a_{i} ai ,在 ① 的数量小于 ③ 的数量 的条件下,必然不如不替换 ( a i + 1 , a i + 1 , ⋯ a j − 1 , a j , a j + 1 ) (a_{i+1},a_{i+1},\cdots\ a_{j-1},a_{j},a_{j+1}) (ai+1,ai+1,⋯ aj−1,aj,aj+1) 这个窗口
所以在这种情况下,分开的区间必然是不如连续区间的
所以可以证明,任意一种不连续的区间,必然可以找到一种连续区间,其窗口权值小于等于不连续的区间。
证毕。
额外
这里为什么可以看出来不如不替换呢?
因为你将一个 a p a_p ap 移动成左端点,如果 p p p 的右半部分数量大于等于左半部分数量,那么你 a i − 1 a_{i-1} ai−1 到达右半部分的距离,就将包括了 a p a_p ap 到达左半部分和右半部分的所有距离了,此外还需要加上新的 a i − 1 a_{i-1} ai−1 到 a i a_i ai 的 k − 1 k-1 k−1 个距离。
对于移动成右端点,如果 p p p 的右半部分数量小于等于左半部分数量,那么你 a j + 1 a_{j+1} aj+1 到达左半部分的距离,就将包括了 a p a_p ap 到达左半部分和右半部分的所有距离了,此外还需要加上新的 a j + 1 a_{j+1} aj+1 到 a j a_j aj 的 k − 1 k-1 k−1 个距离。
所以此时就不如不替换了。