题目大意:一个长度为 $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;
}
标签:int,题解,Medians,mid,中位数,100005,序列,ABC107D,逆序
From: https://www.cnblogs.com/PerchLootie/p/18237792