可恶啊,Easy居然让yzh抢了一步
这是一道有趣的题目
歪解时间
这里先借一下被GM公开处刑的超时代码,方便后面正解的引入
#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[200005],aa[200005];
bool check(int num){
int a_0=0,a_1=0; //后文解释两个变量的作用
for(int i=1;i<=n*2-1;i++){
aa[i]=a[i];
if(aa[i]>=num){
a_1++;
}else{
a_0++;
}
}
for(int i=1;i<n;i++){
if(a_1<=1){
return 0;
}
if(a_0<=1){
return 1;
}
a_0=a_1=0;
for(int j=1;j<=2*n-1-2*i;j++){
int x=aa[j],y=aa[j+1],z=aa[j+2];
if(x>y){
swap(x,y);
}
if(y>z){
swap(y,z);
}
if(x>y){
swap(x,y);
}
aa[j]=y;
if(aa[j]>=num){
a_1++;
}else{
a_0++;
}
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n*2-1;i++){
scanf("%d",&a[i]);
}
int l=1,r=n*2-1;
while(l<r){
int mid=(l+r+1)/2;
if(check(mid)){
l=mid;
}else{
r=mid-1;
}
}
printf("%d",l);
return 0;
}
二分思路很简单,如果当前二分的这个数是大于等于实际答案的,则改变左端点,反之,改变右端点
这里浅浅解释一下a_0
和a_1
的作用
用a_1
统计当前数列中大于等于mid
的数量,a_0
统计当前数列中小于mid
的数量
如果a_1<=1
,说明数列中的数几乎都是小于mid
的,最终得出来的结果肯定是小于mid
的,a_0<=1
同理
明显T到飞起
由于二分本身是一个
O
(
log
n
)
O(\log n)
O(logn) 的算法,那么check
里面只能考虑
O
(
n
)
O(n)
O(n)
正在苦思冥想之时,我的目光注射到了a_0
与a_1
上
正解时间
首先要清楚一个母庸质疑的结论:三个数 a , a , b a,a,b a,a,b 的中位数是 a a a
基于此,我们开始头脑风暴
我们可以按照处理a_0
与a_1
的思路,将原序列转化成一个01串,
0
0
0 表示该数字小于mid
,
1
1
1 表示该数字大于等于mid
将原序列转化成01串后,可以将该01串拆成如下的基本结构:00000
,11111
和01010
为了方便描述,我们分别取名为A
,B
和C
我们讨论一下如下两种情况
A
与B
相邻
如上图,这两个串的中间是一条完美的分水岭,两个串会不断向上伸展,而不向左右伸展
证明很简单
设左边串的右端点为 r r r,右边串的左端点为 l l l,整个串是 S S S 串,转化后的串是 s s s 串
∀ i ( i ≠ l , i ≠ r ) , S i = s i \forall\ i(i\ne l,i\ne r),S_i=s_i ∀ i(i=l,i=r),Si=si
如上的结论应该是显然的,因为 S i − 1 = S i = S i + 1 S_{i-1}=S_i=S_{i+1} Si−1=Si=Si+1
对于 S l S_l Sl ,因为 S l − 1 = S l S_{l-1}=S_l Sl−1=Sl ,根据我们一开始所推得的结论,不难得出 S l = s l S_l=s_l Sl=sl
对于 S r S_r Sr ,因为 S r + 1 = S r S_{r+1}=S_r Sr+1=Sr ,得出 S r = s r S_r=s_r Sr=sr
综上, ∀ i , S i = s i \forall\ i,S_i=s_i ∀ i,Si=si
那么,这第一个结论也就得证了
A
(或B
)与C
相邻
这里考虑A
与C
相邻
我们发现:A
串会一点一点的向C
串延伸,直至“吃掉”整个C
串
证明:
设左边串的右端点为 r r r,右边串的左端点为 l l l,整个串是 S S S 串,转化后的串是 s s s 串
∀ i ( i ≤ r ) , S i = s i \forall\ i(i\le r),S_i=s_i ∀ i(i≤r),Si=si
∀ i ( i > l ) , ∴ S i − 1 = S i + 1 ∵ s i = S i − 1 \forall\ i(i>l),\therefore\ S_{i-1}=S_{i+1}\ \because\ s_i=S_{i-1} ∀ i(i>l),∴ Si−1=Si+1 ∵ si=Si−1
对于 i = l i=l i=l ,因为 S i S_i Si 与 S i + 1 S_{i+1} Si+1 中各有一个 0 0 0 和 1 1 1 ,则 s i s_i si 的取值决定于 S i − 1 S_{i-1} Si−1,所以 s i = S i − 1 s_i=S_{i-1} si=Si−1
综上, ∀ i ( i ≤ r ) , S i = s i , ∀ j ( j ≥ l ) , S j = s j − 1 \forall\ i(i\le r),S_i=s_i,\forall\ j(j\ge l),S_j=s_{j-1} ∀ i(i≤r),Si=si,∀ j(j≥l),Sj=sj−1
得出了这两个结论后,我们可以进行进一步推理
为了后文推理方便,这里定义一个概念:串到点的距离
对于一个串 [ l , r ] [\ l,r\ ] [ l,r ](左端点下标为 l l l,右端点下标为 r r r) ,这个串到下标为 a a a 的点的距离为 min ( a − l , a − r ) \min(a-l,a-r) min(a−l,a−r)
特殊的,如果 l ≤ a ≤ r l\le a\le r l≤a≤r ,则距离为 0 0 0
很显然,当
S
n
S_n
Sn 属于A
串或属于B
串时,就可以确定最终结果了(因为只有A
串和B
串是永恒不变的)
那么, S n S_n Sn 初始时会有两种情况
-
S
n
S_n
Sn 属于
A
串或B
串
此时我们已经能确定答案了,且
S
n
S_n
Sn 到该串(A
串或B
串)的距离为
0
0
0
-
S
n
S_n
Sn 属于
C
串
因为
S
n
S_n
Sn 属于C
串,所以,C
串左右的A
串和B
串可以通过C
串一步步占领
S
n
S_n
Sn,显然,距离
S
n
S_n
Sn 近的一个串会占领
S
n
S_n
Sn
综上,我们不难发现:如果某一个A
串距离
S
n
S_n
Sn 的距离比任何一个B
串距离
S
n
S_n
Sn 的距离都要小,则最终
S
n
S_n
Sn 属于A
串,结果为
0
0
0;如果某一个B
串距离
S
n
S_n
Sn 的距离比任何一个A
串距离
S
n
S_n
Sn 的距离都要小,则最终
S
i
S_i
Si 属于B
串,结果为
1
1
1
所以,只需要求离
S
n
S_n
Sn 最近的A
串到
S
n
S_n
Sn 的距离和离
S
n
S_n
Sn 最近的B
串到
S
n
S_n
Sn 的距离即可
当然,原序列或许会有只有C
串的情况,特判即可
代码如下:
#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[200005],aa[200005];
bool check(int num){
for(int i=1;i<=n+n-1;i++){
if(a[i]>=num){
aa[i]=1;
}else{
aa[i]=0;
}
}
int a_0=2147483647,a_1=2147483647;
for(int i=n;i>=1;i--){ //求离 S_n 最近的A串到 S_n 的距离
if(aa[i]==0&&aa[i-1]==0){
a_0=n-i;
break;
}
}
for(int i=n;i<=n+n-1;i++){
if(aa[i]==0&&aa[i+1]==0){
a_0=min(a_0,i-n);
break;
}
}
for(int i=n;i>=1;i--){ //求离 S_n 最近的B串到 S_n 的距离
if(aa[i]==1&&aa[i-1]==1){
a_1=n-i;
break;
}
}
for(int i=n;i<=n+n-1;i++){
if(aa[i]==1&&aa[i+1]==1){
a_1=min(a_1,i-n);
break;
}
}
if(a_0==2147483647&&a_1==2147483647){ //只有C串
if(aa[n]==0){
if(n%2==0){
return 1;
}
return 0;
}
if(n%2==0){
return 0;
}
return 1;
}
if(a_0==2147483647){ //没有A串
return 1;
}
if(a_1==2147483647){ //没有B串
return 0;
}
if(a_0>a_1){
return 1;
}
return 0;
}
int main(){
scanf("%d",&n);
aa[0]=-1,aa[n+n]=-1;
for(int i=1;i<=n*2-1;i++){
scanf("%d",&a[i]);
}
int l=1,r=n*2-1;
while(l<r){
int mid=(l+r+1)/2;
if(check(mid)){
l=mid;
}else{
r=mid-1;
}
}
printf("%d",l);
return 0;
}