Median - 21杭电多校(6)

Media - 2021“MINIEYE杯”中国大学生算法设计超级联赛(6)

链接 :https://acm.hdu.edu.cn/showproblem.php?pid=7029

Problem Description

Mr. docriz has n different integers 1,2,⋯,n. He wants to divide these numbers into m disjoint sets so that the median of the j-th set is bj. Please help him determine whether it is possible.

Note: For a set of size k, sort the elements in it as c1,c2,⋯,ck, the median of this set is defined as c⌊(k+1)/2⌋.

Input

The first line contains an integer T(1≤T≤1000) - the number of test cases. Then T test cases follow.

The first line of each test case contains 2 integers n,m(1≤m≤n≤105) - the number of integers that Mr. docriz has, and the number of sets he want to divide these numbers into.

The next line contains m integers b1,b2,⋯,bm(1≤bi≤n). It is guaranteed that all the numbers in b are distinct.

It is guaranteed that ∑n≤2×106.

Output

For each test case, output "YES’’ if it is possible to achieve his goal, or "NO’’ otherwise.

Sample Input

3
4 4
2 4 3 1
4 3
1 3 4
4 3
2 3 4

Sample Output

YES
YES
NO

Source

2021“MINIEYE杯”中国大学生算法设计超级联赛(6)

Problem Tutorial: “Median”

显然 b1, b2, · · · , bm 这 m 个数要放在 m 个不同的集合中,剩下的 n − m 个数字要放到这 m 个集合里且不影响每个集合的中位数。使用一个例子以方便说明:假设 n = 6, m = 2, b1 = 3, b2 = 5,那么 1, 2, · · · , n 这些数会被 b 分成 1, 2、 4、 6 这三段,且任意两段中的任意一对数字可以配对消掉。所以最后剩下的所有数字一定是同一段内的。
因此讨论两种情况:

  • 如果长度最大的段的数字个数不大于其它段的数字个数之和,那么最终要么全部消掉,要么剩下一个,且剩下的这个数可以在任何一段内。如果会剩下,不妨将最后一段的数字剩下一个,此时再把最后一段的数字放到中位数最小的集合中即可满足题意,所以答案为 YES。
  • 如果长度最大的段的数字个数大于其它段的数字个数之和,那么最终剩下的所有数字都在最大的这段内。设中位数小于这一段的最小值的集合的个数为 x,容易发现当且仅当 x 不小于这一段剩下的数字个数时有解。

时间复杂度 O(n)。

官方代码

#include <bits/stdc++.h>

using namespace std;

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  int T;
  cin >> T;
  while (T--) {
    int n, m;
    cin >> n >> m;
    assert(m >= 1 && m <= n && n <= 100000);
    vector<int> f(n + 2);
    f[n + 1] = 1;
    while (m--) {
      int v;
      cin >> v;
      assert(v >= 1 && v <= n);
      assert(f[v] == 0);
      f[v] = 1;
    }
    vector<pair<int, int>> size;
    int have = 0;
    int count = 0;
    for (int i = 1; i <= n + 1; i++) {
      if (f[i]) {
        if (have) {
          size.push_back(make_pair(have, count));
        }
        have = 0;
        count += 1;
      } else {
        have += 1;
      }
    }
    sort(size.begin(), size.end());
    if (size.empty()) {
      cout << "YES\n";
      continue;
    }
    int sum = 0;
    for (auto s: size) {
      sum += s.first;
    }
    if (size.back().first <= sum - size.back().first + size.back().second) {
      cout << "YES\n";
    } else {
      cout << "NO\n";
    }
  }
  return 0;
}

官方给出的题解有一个地方不是很理解,下面给出我的题解思路
代码前半部分跟官方题解差不多

  1. 首先是对于中间值下取整,所以在中间值后面插入一个 比中间值大的数 对它没有影响

  2. 还是因为中间值,所以中间值两边同时插入数,也就相当于两两抵消

  3. 最后还是中间值 把这个中间值当作分界线 只要 比它大的数 和 比它小的数 同时存在,就可以两两抵消

  4. 那么扩展一下,多组中间值(需要插入的组数大于3时),这里只有一种情况数字不能全部抵消,就是官方题解中的: 如果长度最大的段的数字个数大于其它段的数字个数之和,这种情况下 长度最大的段的数字 是绝对会剩下的,而当这些剩下的数字个数 大于 比他们小的中间值的个数 时(上面说的第二条),是无解的

  5. 当插入的组数 为 2 时,跟组数为3时 情况少出现了一种 :就是下面代码cnt >= 3 里第一个 if 语句

  6. 组数为1 时,只有个数 大于 中间值小于插入的这一段的最小值的集合的个数 ,才能有解

AC代码

#include <bits/stdc++.h>
using names
pace std;

const int N = 2e5+10;

int b[N]={0}, cnt = 0, sum , mxx, id, x[N]; 
// b[]:中间值数组,cnt:要插入的段的个数,sum:要插入的数字个数之和
// mxx:最长的插入段,id:最长插入段的位置,x[]不用管

int main(){
    int t;  scanf("%d",&t); 
    while(t--){
        cnt = 0, sum = 0, mxx = 0, id = 0;
        int n , m ; scanf("%d%d",&n,&m); 
        for(int i = 1;i <= m; i++)  scanf("%d",&b[i]); 

		// 排序 计算要插入的数
        sort(b+1, b + m + 1);
        b[0] = 0, b[m+1] = n+1;

		//找出要插入的每组长度,以及所在位置, x[]数组不用管,只用到了dx          
        for(int i = 1; i <= m+1; i++) {  
            int dx = b[i] - b[i-1] - 1;
            if( dx > 0 ) x[++cnt] = dx;
            if(mxx < dx) mxx = dx, id = i-1; 
            sum += dx;
        }
		
		//分情况讨论 
        if(cnt >= 3){
            if(mxx <= sum - mxx) cout<<"YES"<<endl;
            else if(mxx - sum + mxx <= id) cout<<"YES"<<endl; 
            else cout<<"NO"<<endl; 
        }
        else if(cnt == 2) {
            if(mxx > sum - mxx +id) cout<<"NO"<<endl;
            else cout<<"YES"<<endl;
        }
        else if(cnt == 1){
            if(mxx <= id) cout<<"YES"<<endl;
            else cout<<"NO"<<endl;
        }
        else cout<<"YES"<<endl;
    }    

    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
你好!感谢您对ChitGPT的信任。对于您的问题,我可以为您提供以下回答: ``` public static int linearSelect(int[] S, int k) { if (S.length == 1) { return S[0]; } // Divide S into groups of 5 elements each int[] group = new int[5]; int[] medians = new int[(int) Math.ceil((double) S.length / 5)]; for (int i = 0; i < S.length; i += 5) { int endIndex = i + 5; if (endIndex > S.length) { endIndex = S.length; } for (int j = i; j < endIndex; j++) { group[j - i] = S[j]; } // Sort the group of 5 elements Arrays.sort(group); // Find the median of the group medians[i / 5] = group[2]; } // Recursively find the median of the medians int medianOfMedians = linearSelect(medians, (medians.length + 1) / 2); // Partition S around the median-of-medians int[] left = new int[S.length]; int[] right = new int[S.length]; int leftIndex = 0, rightIndex = 0; for (int i = 0; i < S.length; i++) { if (S[i] < medianOfMedians) { left[leftIndex++] = S[i]; } else if (S[i] > medianOfMedians) { right[rightIndex++] = S[i]; } } // Determine which partition to recurse on if (k <= leftIndex) { return linearSelect(Arrays.copyOf(left, leftIndex), k); } else if (k == leftIndex + 1) { return medianOfMedians; } else { return linearSelect(Arrays.copyOf(right, rightIndex), k - leftIndex - 1); } } ``` 该算法将数组元素分成多个较小的组,并找到每个组的中位数。接下来,它递归地在这些中位数中找到中位数,直到找到一个特定元素。然后,它根据小于、等于和大于中位数的元素将数组分成三个部分,并递归地执行算法,直到它找到了第 k 个最小元素。 该算法的时间复杂度为 O(n),它实现了线性时间选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值