[ABC107D] Median of Medians 題解

PerchLootie發表於2024-06-07

題目大意:一個長度為 $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;
}

相關文章