Median Pyramid Hard题解

本文解析了一道关于利用二分法快速判断数列中位数的难题,通过巧妙地转化和分析数列结构,提出了一种高效算法。文章详细讲解了如何利用01串表示法简化问题,并给出了两种特殊情况下的证明。最后,给出了优化后的代码实现和关键步骤的总结。
摘要由CSDN通过智能技术生成

可恶啊,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_0a_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_0a_1

正解时间

首先要清楚一个母庸质疑的结论:三个数 a , a , b a,a,b a,a,b 的中位数是 a a a

基于此,我们开始头脑风暴

我们可以按照处理a_0a_1的思路,将原序列转化成一个01串, 0 0 0 表示该数字小于mid 1 1 1 表示该数字大于等于mid

将原序列转化成01串后,可以将该01串拆成如下的基本结构:000001111101010

为了方便描述,我们分别取名为ABC

我们讨论一下如下两种情况

  1. AB相邻

在这里插入图片描述

如上图,这两个串的中间是一条完美的分水岭,两个串会不断向上伸展,而不向左右伸展

证明很简单

设左边串的右端点为 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} Si1=Si=Si+1

对于 S l S_l Sl ,因为 S l − 1 = S l S_{l-1}=S_l Sl1=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

那么,这第一个结论也就得证了

  1. A(或B)与C相邻

这里考虑AC相邻

这里暂时没有图

我们发现: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(ir),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), Si1=Si+1  si=Si1

对于 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} Si1,所以 s i = S i − 1 s_i=S_{i-1} si=Si1

综上, ∀   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(ir),Si=si, j(jl),Sj=sj1

得出了这两个结论后,我们可以进行进一步推理

为了后文推理方便,这里定义一个概念:串到点的距离

对于一个串 [   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(al,ar)

特殊的,如果 l ≤ a ≤ r l\le a\le r lar ,则距离为 0 0 0

很显然,当 S n S_n Sn 属于A串或属于B串时,就可以确定最终结果了(因为只有A串和B串是永恒不变的)

那么, S n S_n Sn 初始时会有两种情况

  1. S n S_n Sn 属于A串或B

此时我们已经能确定答案了,且 S n S_n Sn 到该串(A串或B串)的距离为 0 0 0

  1. 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值