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;
}
官方给出的题解有一个地方不是很理解,下面给出我的题解思路
代码前半部分跟官方题解差不多
-
首先是对于中间值下取整,所以在中间值后面插入一个 比中间值大的数 对它没有影响
-
还是因为中间值,所以中间值两边同时插入数,也就相当于两两抵消
-
最后还是中间值 把这个中间值当作分界线 只要 比它大的数 和 比它小的数 同时存在,就可以两两抵消
-
那么扩展一下,多组中间值(需要插入的组数大于3时),这里只有一种情况数字不能全部抵消,就是官方题解中的:
如果长度最大的段的数字个数大于其它段的数字个数之和
,这种情况下 长度最大的段的数字 是绝对会剩下的,而当这些剩下的数字个数 大于 比他们小的中间值的个数 时(上面说的第二条),是无解的 -
当插入的组数 为 2 时,跟组数为3时 情况少出现了一种 :就是下面代码cnt >= 3 里第一个 if 语句
-
组数为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;
}