题目大意
给定一个长度为$n$的序列$a$,求出长度至少为$k$的子序列,使得:
所有下标为奇数的元素的最大值与所有下标为偶数的元素的最大值的最小值最小
即$min(max(s_1,s_3,s_5…),max(s_2,s_4,s_6…))$
$2 ≤k≤2\times10^5$
$Time\ Limit:2s$
$input:$
4 2
1 2 3 4
$output:$
1
思路
我们如果考虑枚举每种组合方式,其复杂度是指数级的。
那么我们考虑能不能将这个问题转化为一个判定问题?
即$a_i$为在奇数位/偶数位时的最大值能否构造出一个符合条件的$s$
那么,我们可以$O(n)$的时间$check$:
对于在奇数位,即在$a$中找到不连续的至少$k-\lfloor\frac{k}{2}\rfloor$个不大于$a_i$的数
(且对于最后一位,如果$k$一共有偶数位而其小于$a_i$的数恰好有$k-\lfloor\frac{k}{2}\rfloor$个,而最后一位是$n$则也是不合法的,对于偶数位的check也同理)
证明:若当前满足条件的有$c$个数(即小于等于$a_i$),且$c≥k$
如果$k\ mod\ 2\ ==\ 0$ 那么$\lfloor\frac{k}{2}\rfloor=\frac{k}{2}$
而在$c$中间至少存在$c-\frac{k}{2}-1$个间隔可以插入偶数项,且可以保证最后一项为偶数项。
而因为$c≥k$,因此保证$c≥\frac{k}{2}+1$,而其最后一位为偶数项。
因此必定合法。
对于在偶数位,即在$a$中找到不连续的至少$\lfloor\frac{k}{2}\rfloor$个不大于$a_i$的数
因此我们可以遍历一遍$a$进行$check$
我们的答案就是所有合法的方案中最小的$a_i$。
但是这种$check$有一种问题,我们选的是最多的,但选的方案中不一定包含$a_i$。但任意一种不包含$a_i$的方案必然可以转化为最大值为小于$a_i$的一种方案,因此对取$min$是没有影响的。
bool check(int x,int state){
if (state == 1){
int c = 0,right = 0;
for (int i = 1; i <= n; i++){
if (a[i] <= x){
right = i;
c++;
i++;
//if (i <= n)b++;
}
}
if (c == k - k / 2 && right == n && k % 2 == 0) return false;
return c >= k - k / 2;
}else{
int c = 0,right = 0;
for (int i = 2; i <= n; i++){
//cout << i << endl;
if (a[i] <= x){
right = i;
c++;
i++;
//if (i <= n)c++;
}
}
//cout << right << endl;
if (c == k / 2 && right == n && k % 2 != 0) return false;
return c >= k / 2;
}
}
而对于我们所找到的这段不连续的数,如果其可以找到对应的$s_1,s_3,s_5…$,而我们的目标为$min(max(s_1,s_3,s_5…),max(s_2,s_4,s_6…))$
而对于$max(s_2,s_4,s_6…)$,因为在$a$中一定存在至少$k$个不连续的且小于$a_i$的元素,因此在这些元素之间至少可以包含$\lfloor\frac{k}{2}\rfloor$个元素,而这些元素可以当偶数位。且对应着一种合法的偶数方案。
因此我们可以朴素枚举$a_i$,每次用$a_i$去取$min$更新$ans$,复杂度$O(n^2)$。
那么我们已经把这个问题的求解转化为了判定,那么此时就应该证明其单调性,试图用二分将复杂度降到$O(n\log n)$。
单调性证明:
我们要证明的命题如下:
命题一:对于任一合法的$a_i$,则对于任何大于等于$a_i$的$a_j$都合法
命题二:对于任一不合法的$a_i$,则对于任何小于等于$a_i$的$a_j$都不合法
对于命题一:
如果$a_i$为奇数项
如果对于$a_i$来说,设其满足不连续的不大于$a_i$的数的个数为$c_i$,对于$c_i$,最小为$k-\lfloor\frac{k}{2}\rfloor$,且$a_j$为大于等于$a_i$的正整数集合中最小的元素。
如果$k-\lfloor\frac{k}{2}\rfloor$
那么这$k$个数必然不相邻
若不存在相邻的情况,则$k-\lfloor\frac{k}{2}\rfloor+1$
而对于$a_j$,在满足$a_i$的集合中,至多有两个元素与$a_j$相邻。
此时,$c_j=k-\lfloor\frac{k}{2}\rfloor-1$
那么如果$a_j$为偶数项,则必定满足
当$\lfloor \frac{k}{2} \rfloor=\frac{k}{2}-1$时(即$k$一共有奇数项)
$c_j=k-\frac{k}{2}$(则$a_j$作奇数项时一定合法)
当$\lfloor \frac{k}{2} \rfloor=\frac{k}{2}$时(即$k$一共有偶数项)
$c_j=k-\frac{k}{2}-1$
而它既不能放在奇数项,又不能放在偶数项是不是就不合法了呢?
但是这并不影响二分的最终结果
因为最多会有两个在$c_j$两边的小于$a_j$的值,而如果我们选取不选$a_j$,仍然可以保证其所选的数大于等于$k$,而因为它比$a_i$大,必然不会成为答案,而答案一定在$a_j$的左边,即对于任一$a_j$只要其非连续地小于等于它的数大于等于特定值,答案一定在$a_j$的左边,
而对于命题二,同样地如果$a_i$不合法有以下两种可能:
1.不大于$a_i$的数的个数不足$k$
2.小于$a_i$的数不能够满足不连续分布
首先对于1,$a_j$必然不可能
而对于2,因为我们每次找的都是连续的,不超过$a_i$的,而$a_i$没找到,则$a_j$必然找不到。
因此我们的$check$是满足单调性的。
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 2e5 + 10;
int a[maxn],b[maxn];
int n,k;
bool check(int x,int state){
if (state == 1){
int c = 0,right = 0;
for (int i = 1; i <= n; i++){
if (a[i] <= x){
right = i;
c++;
i++;
//if (i <= n)b++;
}
}
if (c == k - k / 2 && right == n && k % 2 == 0) return false;
return c >= k - k / 2;
}else{
int c = 0,right = 0;
for (int i = 2; i <= n; i++){
//cout << i << endl;
if (a[i] <= x){
cout << a[i] << " ";
right = i;
c++;
i++;
//if (i <= n)c++;
}
}
//cout << right << endl;
if (c == k / 2 && right == n && k % 2 != 0) return false;
return c >= k / 2;
}
}
int main(){
cin >> n >> k;
for (int i = 1; i <= n; i++){
cin >> a[i];
b[i] = a[i];
}
sort(b + 1,b + n + 1);
int l = 1,r = n;
while(l < r){
int mid = r + l >> 1;
cout << l << " " << r << " " << mid << endl;
if (check(b[mid],0) || check(b[mid],1)) r = mid;
else l = mid + 1;
}
cout << b[l];
//system("PAUSE");
return 0;
}