ARC B the median of the median

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/oidoidoid/article/details/82117911

题目

题目的意思是对于序列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);
}

线段树

猜你喜欢

转载自blog.csdn.net/oidoidoid/article/details/82117911