【每日一题】补档 CF371E. Subway Innovation | 滑动窗口 | 中等(中等给的是证明)

文章讨论了一个关于数组操作的问题,目标是在给定的整数数组中选择k个连续数,使得这些数对之间的差绝对值之和最小。通过排序数组并使用动态规划策略,计算不同窗口大小的差值绝对值和,找出最小窗口作为答案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目内容

原题链接

给定一个长度为 n n n 的数组 a a a ,现在需要你从中选择 k k k个数,使得选择的 k k k 个数构成的 k × ( k − 1 ) 2 \frac{k\times (k-1)}{2} 2k×(k1) 个数对的差的绝对值之和最小,请你输出选择的 k k k 个数,如果有多种答案,输出任意一种即可。

数据范围

  • 3 ≤ n ≤ 3 ⋅ 1 0 5 3\leq n\leq 3\cdot 10^5 3n3105
  • − 1 0 8 ≤ a i ≤ 1 0 8 -10^8\leq a_i\leq 10^8 108ai108
  • 2 ≤ k ≤ n − 1 2\leq k\leq n-1 2kn1

题解

这里我们先将 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=lrar+1ai=(rl+1)×ar+1i=lrai

删除一个窗口中的数 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+1r+1aial=(i=l+1r+1ai)(rl+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,,aj1,aj ,满足 j − i + 1 = k j-i+1=k ji+1=k

如果你认为非连续区间的窗口权值可能更小?

不妨将区间拆成三部分:

  • ( a i , a i + 1 , ⋯   , a p − 1 ) (a_i,a_i+1,\cdots,a_{p-1}) (ai,ai+1,,ap1)
  • ( 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} ai1 或者 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} ai1 左边的数,必然不如替换成 a i − 1 a_{i-1} ai1

因为这两个数分别是新的窗口的最左端点和最右端点了,窗口左端点一味地小,右端点一味地大,必然使得窗口权值变大。

所以来考虑替换成 a i − 1 a_{i-1} ai1 或者 a j + 1 a_{j+1} aj+1 的情况

  • 替换成 a i − 1 a_{i-1} ai1

    • 如果 ① 的数量小于等于 ③ 的数量,必然不如不替换
    • 如果 ① 的数量大于 ③ 的数量,这个窗口权值和 ( a i − 1 , a i , ⋯   a j − 2 , a j − 1 ) (a_{i-1},a_i,\cdots\ a_{j-2},a_{j-1}) (ai1,ai, aj2,aj1) 这个窗口权值相比呢?
      • 相当于将 ( a i − 1 , a i , ⋯   a j − 2 , a j − 1 ) (a_{i-1},a_i,\cdots\ a_{j-2},a_{j-1}) (ai1,ai, aj2,aj1) 这个窗口的 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}) (ai1,ai, aj2,aj1) 这个窗口
  • 替换成 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, aj1,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, aj1,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, aj1,aj,aj+1) 这个窗口

所以在这种情况下,分开的区间必然是不如连续区间的

所以可以证明,任意一种不连续的区间,必然可以找到一种连续区间,其窗口权值小于等于不连续的区间。

证毕。

额外

这里为什么可以看出来不如不替换呢?

因为你将一个 a p a_p ap 移动成左端点,如果 p p p 的右半部分数量大于等于左半部分数量,那么你 a i − 1 a_{i-1} ai1 到达右半部分的距离,就将包括了 a p a_p ap 到达左半部分和右半部分的所有距离了,此外还需要加上新的 a i − 1 a_{i-1} ai1 a i a_i ai k − 1 k-1 k1 个距离。

对于移动成右端点,如果 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 k1 个距离。

所以此时就不如不替换了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值