Range and Partition(双指针/前缀和/贪心)

题目
题意:给定长度为 n n n的数组 a a a k k k,其中 1 < = a i < = n 1<=a_i<=n 1<=ai<=n现在要求找出区间长度最小的 x , y x,y x,y,将数组 a a a划分成k个子数组,其中 a i a_i ai位于这k个子数组的其中之一。要求每个子数组中,位于 [ x , y ] [x,y] [x,y]范围内的元素,多于不在 [ x , y ] [x,y] [x,y]范围内的元素。
现在构造出x,y,以及这k个子数组的区间。
官方题解
思路:考虑x,y已知的情况。对于每个 a i a_i ai,如果 x < = a i < = y x<=a_i<=y x<=ai<=y,则+1,否则记为-1。那么要求每个子数组累计得分要为正。因此,我们初始得分为0,当得分首次大于0时,则截取当前区间,并开始取新的区间,得分重新归零。注意最后一个区间(即第k个子数组)保留。这里用的是前缀和的思想。

如何确定x,y。不妨设上述得分数组为 b b b,前缀和数组为 p r e pre pre。我们可以发现 b b b取值为1,-1;从而 p r e pre pre相邻元素差值绝对值为1,即 ∣ p r e i − 1 − p r e i ∣ = 1 |pre_{i-1}-pre_i|=1 prei1prei=1。要使按照上述贪心思路,能找到k组子数组,说明 p r e n pre_n pren至少要有k,即 p r e n > = k pre_n>=k pren>=k;此外,由于 p r e pre pre是连续变化的,我们必然可以找到 p r e pre pre取值为1,2,…,k的下标,我们取它们出现的第一次位置即可。

要使 p r e n > = k pre_n>=k pren>=k,则有 2 ∗ ∑ i = 1 n [ x < = a i < = y ] − n > = k 2*\sum_{i=1}^{n}[x<=a_i<=y] - n>=k 2i=1n[x<=ai<=y]n>=k,有 ∑ i = 1 n [ x < = a i < = y ] > = ⌈ n + k 2 ⌉ \sum_{i=1}^{n}[x<=a_i<=y] >=\lceil \frac{n+k}{2}\rceil i=1n[x<=ai<=y]>=2n+k
我们可以对数组排序,枚举x从小到大,查看对应有 ⌈ n + k 2 ⌉ \lceil \frac{n+k}{2}\rceil 2n+k数量的y的最小取值,所有的情况中,取 y − x y-x yx最小的。

官方代码,写的真好

#include<bits/stdc++.h>
using namespace std;
 
int main() {
 
    int tc;
    cin >> tc;
    while( tc-- ){
 
        int n, k;
        cin >> n >> k;
 
        vector<int> a(n), sorted_a(n);
        for(int i=0; i<n; i++){
            cin >> a[i];
            sorted_a[i] = a[i];
        }
 
        sort(sorted_a.begin(),sorted_a.end());
 
        int req_sum = ( n + k + 1 ) / 2;
 
        pair<int,pair<int,int>> ans = { n + 1 , { -1 , -1 } };
 
        for(int i=0; i+req_sum-1<n; i++)
            ans = min( ans , { sorted_a[i+req_sum-1] - sorted_a[i] , { sorted_a[i] , sorted_a[i+req_sum-1] } } );
 
        cout << ans.second.first << ' ' << ans.second.second << '\n';
 
        int subarrays_found = 0, curr_sum = 0;
        int last_uncovered = 1;
 
        for(int i=0; i<n; i++){
 
            if( a[i] >= ans.second.first && a[i] <= ans.second.second ) curr_sum ++;
                else curr_sum --;
 
            if( curr_sum > 0 && subarrays_found + 1 < k ){
 
                cout << last_uncovered << ' ' << ( i + 1 ) << '\n';
                last_uncovered = i + 2;
 
                subarrays_found ++;
                curr_sum = 0;
            }
        }
 
        subarrays_found ++;
        cout << last_uncovered << ' ' << n << '\n';
 
    }
 
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值