题意
求序列的所有子区间的中位数所组成的可重集的中位数。
题解
考虑如何求一个子区间的中位数:考虑二分答案,假设当前二分出的中位数为 (mid) ,求出这个子区间内比 (mid) 小的数的个数 (cnt) ,若 (cnt) 大于区间长度的一半,那么中位数比 (mid) 小,否则比 (mid) 大。
求所有子区间的中位数的中位数也可以使用这种方法。先二分答案 (mid) ,求出区间内小于等于 (mid) 的数的个数小于等于子区间个数的一半的子区间个数 (cnt),若 (cnt) 大于所有子区间个数,则中位数比 (mid) 小,否则比 (mid) 大。
考虑如何求解 (cnt) :
做一个前缀和 (p_i) ,表示前 (i) 个数中小于等于 (mid) 的数的个数。那么若区间 ([l+1,r]) 的中位数小于等于 (mid) ,有 (2 imes (p_r-p_l)>r-l) ,移项得 (2 imes p_r-r > 2 imes p_l-l) 。这是经典的逆序对问题,可以通过树状数组求解。
另外有一处细节:每次check的时候要先加入 (2 imes p_0-0) ,否则统计不到区间 ([1,i]) 的答案。然而原题数据太水了,没有卡这种情况,加了hack数据后 这篇博客 和两份赛时AC的代码均被hack了。
( ext{Code}:)
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long lxl;
const int maxn=1e5+5;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
template <typename T>
inline void read(T &x)
{
x=0;T f=1;char ch=getchar();
while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
int n,a[maxn];
int b[maxn],m;
namespace BIT
{
int sum[maxn<<1];
inline int lowbit(int x) {return x&-x;}
inline void add(int x,int d)
{
for(int i=x;i<=(2*n+1);i+=lowbit(i))
sum[i]+=d;
}
inline int query(int x)
{
int res=0;
for(int i=x;i>=1;i-=lowbit(i))
res+=sum[i];
return res;
}
inline void clear()
{
memset(sum,0,sizeof(int)*(2*n+5));
}
}
int p[maxn];
inline lxl check(int x)
{
lxl res=0;
BIT::clear();
for(int i=1;i<=n;++i)
p[i]=p[i-1]+(a[i]<=x);
BIT::add(n+1,1);
for(int i=1;i<=n;++i)
{
res+=BIT::query(2*p[i]-i-1+n+1);
BIT::add(2*p[i]-i+n+1,1);
}
return res;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("median.in","r",stdin);
freopen("median.out","w",stdout);
#endif
read(n);
for(int i=1;i<=n;++i)
read(a[i]),b[i]=a[i];
sort(b+1,b+n+1);
m=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;++i)
a[i]=lower_bound(b+1,b+m+1,a[i])-b;
int l=1,r=m,ans=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)>1ll*n*(n+1)/4) r=mid-1,ans=mid;
else l=mid+1;
}
printf("%d
",b[ans]);
return 0;
}