题目的意思是对于序列a1,a2,……,an,求其中所有子串的中位数组成的序列的中位数。
1.在比赛的时候想到了二分答案,但是对于如何进行验证完全没有头绪
2.二分答案,对于每个枚举的答案数,求中位数比他小的字串的个数和以其为中位数的字串的个数,就可以通过和n*(n-1)/2+1比较判断该数是不是答案
3.实现:将所有>所验证数的答案全都赋值成1,所有<=验证数的答案全都赋值成-1,求前缀和,可见一对i、j满足sum[i]-sum[j]<0即代表一个中位数比验证数小的字串,故问题转换成求逆序对的问题,用线段树可解,效率大概为O(nlog^2(n))
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<queue>
#include<map>
#include<stack>
#define go(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define ll long long
#define N 100005
using namespace std;
ll a[N],a1[N],b[N],n,sum,t[N*8];
void insert(int k, int l, int r, int pos){
if (l>pos||r<pos) return;
if (l==r&&r==pos){
t[k]++;
return;
}
ll m=(l+r)/2;
insert(k*2,l,m,pos);insert(k*2+1,m+1,r,pos);
t[k]=t[k*2]+t[k*2+1];
};
ll query(int k, int l, int r, int left, int right){
if (l>right||r<left){
return 0;
}
if (left<=l&&r<=right){
return t[k];
}
ll m=(l+r)/2;
return query(k*2,l,m,left,right)+query(k*2+1,m+1,r,left,right);
}
bool check(ll mdi){
ll maxn=N;
b[0]=N;
go(i,1,n){
if (a[i]>mdi) b[i]=b[i-1]+1;
else b[i]=b[i-1]-1;
maxn=max(maxn,b[i]);
}
memset(t,0,sizeof(t));
ll tot=0;
insert(1,1,maxn,N);
go(i,1,n){
tot+=(b[i]==maxn?0:query(1,1,maxn,b[i]+1,maxn));
insert(1,1,maxn,b[i]);
}
return (tot>=n*(n+1)/2/2+1);
}
/*
用这个可以直接求的是中位数肯定比这个数小的1 2 3 4
*/
ll doit(){
ll l=1,r=sum;
while (l!=r){
ll m=(l+r)/2;
if (check(a1[m])){
r=m;
}
else {
l=m+1;
}
}
return a1[l];
}
int main(){
scanf("%d",&n);
go(i,1,n){
scanf("%d",&a[i]);
a1[i]=a[i];
}
sort(a1+1,a1+1+n);
sum=0;
go(i,1,n){
if (i==1||a1[i-1]!=a1[i])sum++;
a1[sum]=a1[i];
}
ll ans=doit();
printf("%lld\n",ans);
}
线段树