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