[ABC107D] Median of Medians 题解

题目大意:一个长度为 \(M\) 的序列的中位数为这个序列从小到大排序后第 \(\lfloor\frac M 2\rfloor + 1\) 个数,将这个序列的所有子段的中位数放入一个序列中,求这个序列的中位数。

设一个序列 \(a\) 的中位数为 \(x\),那么 \(a\) 中至少会有一半的数大于等于 \(x\),并且 \(x\)\(a\) 中满足这个条件的数中最大的,因此答案满足单调性,考虑二分答案。
同时也不难发现,上述所求答案满足: \(i<j\) 并且 \(a_i\leq a_j\) 的数对数量大于等于总对数的一半。

运用差分与前缀和思想,对于每一次二分找到的中间位置,将已排列好的原序列的中间位置的值与未排序序列中的元素作比较,如果该值比原序列的中间位置的值小则标记为 \(-1\),否则标记为 \(1\),同时对标记数组求前缀和。

现在问题转化为了在前缀和数组里求 \(i<j\) 并且 \(a_i\leq a_j\) 的数对数量是否大于等于总对数的一半,我们发现这种数对的数量等于总对数减去 \(i<j\) 并且 \(a_i>a_j\) 的数对数量,这不就是逆序对吗?

设序列元素个数为 \(n\),运用归并排序思想求出逆序对数量,再判断总对数 \(\frac{n(n+1)}{2}\) 减去逆序对数量是否大于总对数数量的一半即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int a,s[100005],d[100005],f[100005],g[100005];
int ans,sum,num;
int n[100005],m[100005];
void merge(int x,int y){
	if(x==y) return ;
	int mid=(x+y)/2;
	merge(x,mid);
	merge(mid+1,y);
	int i=x,j=mid+1,k=x;
	while(i<=mid&&j<=y){
		if(f[i]<=f[j]){
			m[k++]=f[i++];
		}
		else{
			m[k++]=f[j++];
			sum+=mid-i+1;
		}
	}
	while(i<=mid) m[k++]=f[i++];
	while(j<=y) m[k++]=f[j++];
	for(int i=x;i<=y;i++) f[i]=m[i];
}
bool check(int x){
    for(int i=1;i<=a;i++){
        if(s[i]<g[x]){
            f[i]=f[i-1]-1;
        }
        else {
            f[i]=f[i-1]+1;
        }
    }
    merge(0,a);
    if(num-sum>=num/2){
        return true;
    }
    return false;
}
signed main(){
	scanf("%lld",&a);
    num=a*(a+1)/2;
	for(int i=1;i<=a;i++){
		scanf("%lld",&s[i]);
        g[i]=s[i];
	}
    sort(g+1,g+a+1);
	int l=1,r=a;
	while(l<=r){
		int mid=(l+r)/2;
		if(check(mid)){
			ans=g[mid];
            l=mid+1;
		}
		else{
			r=mid-1;
		}
        sum=0;
	}
    printf("%lld",ans);
	return 0;
}
posted @   Rannio  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示