洛谷 P1168 中位数

洛谷 P1168 中位数

题目描述

给出一个长度为N的非负整数序列Ai,对于所有1 ≤ k ≤ (N + 1) / 2,输出A1, A3, …, A2k-1的中位数。即前1,3,5,…1,3,5,…个数的中位数。

输入输出格式
输入格式:

第1行为一个正整数N,表示了序列长度。

第2行包含N个非负整数Ai(Ai≤ 10^9)

输出格式:

共(N + 1) / 2行,第i行为A1, A3, …, A2k-1的中位数。


第一反应—排序

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100005],b[100005];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;++i)
    {
        scanf("%d",&a[i]);
    }
    int k=0;
    while(k<n)
    {
        memset(b,0,sizeof(b));
        for(int i=0;i<=k;++i)
        {
            b[i]=a[i];
        }
        sort(b,b+k+1);
        printf("%d\n",b[(k+1)/2]);
        k+=2;
    }
    return 0;
} 

可想而知的TLE了


二分查找 手动插入

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100005],b[100005];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;++i)
    {
        scanf("%d",&a[i]);
    }
    int k=2;
    memset(b,0,sizeof(b));
    b[0]=a[0];
    printf("%d\n",b[0]);
    while(k<n)
    {
        int l=0,r=k-1;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(b[mid]<a[k-1])
                l=mid+1;
            else
                r=mid;
        }
        for(int i=k-2;i>=l;--i)
        {
            b[i+1]=b[i];
        }
        b[l]=a[k-1];
        l=0,r=k;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(b[mid]<a[k])
                l=mid+1;
            else
                r=mid;
        }
        for(int i=k-1;i>=l;--i)
        {
            b[i+1]=b[i];
        }
        b[l]=a[k];
        printf("%d\n",b[(k+1)/2]);
        k+=2;
    }
    return 0;
} 

然而和排序并没有什么差别…

后来改进了插入,把两次插入并为一次
大概就是记下两次查找到的位置,到后面一起调整插入

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100005],b[100005];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;++i)
    {
        scanf("%d",&a[i]);
    }
    int k=2;
    int k1,k2,mi,ma;
    memset(b,0,sizeof(b));
    b[0]=a[0];
    printf("%d\n",b[0]);
    while(k<n)
    {
        int l=0,r=k-1;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(b[mid]<a[k-1])
                l=mid+1;
            else
                r=mid;
        }
        k1=l;
        l=0,r=k-1;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(b[mid]<a[k])
                l=mid+1;
            else
                r=mid;
        }
        k2=l;
        mi=k1;
        ma=k2;
        if(mi>ma)
            swap(mi,ma);
        for(int i=k-2;i>=mi;--i)
        {
            if(i>=ma)
            {
                b[i+2]=b[i];
                //printf("%d %d\n",i+2,i);
            }
            else if(i>=mi)
            {
                b[i+1]=b[i];
                //printf("%d %d\n",i+1,i);
            }
        }
        if(a[k]>a[k-1])
            k2++;
        else
            k1++;
        //k1k2的调整要后面做 先加的话移动会受影响 
        b[k1]=a[k-1];
        b[k2]=a[k];
        printf("%d\n",b[(k+1)/2]);
        k+=2;
    }
    return 0;
} 

时间是少了点 结果还是差不多


丧心病狂的算法(我也不知道该叫它什么…)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[100005],b[100005];
int main(){
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;++i)
    {
        scanf("%d",&a[i]);
    }
    int k=2;
    memset(b,0,sizeof(b));
    b[0]=a[0];
    printf("%d\n",b[0]);
    while(k<n)
    {
       
        int i=k-1;
        while(i>0 && a[i]<a[i-1])
        {
            swap(a[i],a[i-1]);
            i--;
        }
        i=k;
        while(i>0 && a[i]<a[i-1])
        {
            swap(a[i],a[i-1]);
            i--;
        }
        printf("%d\n",a[(k+1)/2]);
        k+=2;
    }
    return 0;
} 

再后来,插入用了vector的insert

iterator insert( iterator loc, const TYPE &val );
void insert( iterator loc, size_type num, const TYPE &val );
void insert( iterator loc, input_iterator start, input_iterator end );
insert() 函数有以下三种用法:
在指定位置loc前插入值为val的元素,返回指向这个元素的迭代器,
在指定位置loc前插入num个值为val的元素
在指定位置loc前插入区间[start, end)的所有元素 .

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
vector <int> v;
int main(){
    int n;
    int a,b;
    scanf("%d",&n);
    scanf("%d",&a);
    v.push_back(a);
    printf("%d\n",v[0]);
    int k=2;
    while(k<n)
    {
    	scanf("%d%d",&a,&b);
        int l=0,r=k-1;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(v[mid]<a)
                l=mid+1;
            else
                r=mid;
        }
        v.insert(v.begin()+l,a);
        l=0,r=k;
        while(l<r)
        {
            int mid=(l+r)/2;
            if(v[mid]<b)
                l=mid+1;
            else
                r=mid;
        }
        v.insert(v.begin()+l,b);
        printf("%d\n",v[(k+1)/2]);
        k+=2;
    }
    return 0;
} 

还有一种用STL的二分的

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;
vector <int> v;
int main(){
    int n;
    int a,b;
    scanf("%d",&n);
    scanf("%d",&a);
    v.push_back(a);
    printf("%d\n",v[0]);
    int k=2;
    while(k<n)
    {
    	scanf("%d%d",&a,&b);
        v.insert(upper_bound(v.begin(),v.end(),a),a);
        v.insert(upper_bound(v.begin(),v.end(),b),b);
        printf("%d\n",v[(k+1)/2]);
        k+=2;
    }
    return 0;
} 

于是神奇地过了…


正解(讲解摘自洛谷题解)

使用两个堆,大根堆维护较小的值,小根堆维护较大的值
即小根堆的堆顶是较大的数中最小的,大根堆的堆顶是较小的数中最大的
将大于大根堆堆顶的数(比所有大根堆中的元素都大)的数放入小根堆,小于等于大根堆堆顶的数(比所有小根堆中的元素都小)的数放入大根堆
那么就保证了所有大根堆中的元素都小于小根堆中的元素
于是我们发现对于大根堆的堆顶元素,有【小根堆的元素个数】个元素比该元素大,【大根堆的元素个数-1】个元素比该元素小;
同理,对于小跟堆的堆顶元素,有【大根堆的元素个数】个元素比该元素小,【小根堆的元素个数-1】个元素比该元素大;
那么维护【大根堆的元素个数】和【小根堆的元素个数】差值不大于1之后,元素个数较多的堆的堆顶元素即为当前中位数;(如果元素个数相同,那么就是两个堆堆顶元素的平均数,本题不会出现这种情况)
根据这两个堆的定义,维护方式也很简单,把元素个数多的堆的堆顶元素取出,放入元素个数少的堆即可

#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
#include <cmath>
using namespace std;
struct cmp
{
	bool operator() (int a,int b)
	{
		return a>b;//小根堆 
	}
};
priority_queue<int,vector<int> > q1;//大根堆 
priority_queue<int,vector<int>,cmp > q2;//小根堆 
int main(){
	int n;
	int k;
	scanf("%d",&n);
	scanf("%d",&k);
	q1.push(k);//要先放一个元素进去 
	printf("%d\n",q1.top());
	for(int i=1;i<n;++i)
	{
		scanf("%d",&k);
		if(k>q1.top())
			q2.push(k);
		else
			q1.push(k);
		int s1=q1.size(),s2=q2.size();
		while(s1-s2>1 || s2-s1>1)
		//(q1.size()-q2.size())>1 || (q2.size()-q1.size())>1) 不知道为什么不能这样写
		//size()也不可以直接用abs 
		{
			if(s1>s2)
			{
				q2.push(q1.top());
				q1.pop();
			}
			else
			{
				q1.push(q2.top());
				q2.pop();
			}
			s1=q1.size();
			s2=q2.size();
		}
		if((i+1)&1)
		{
			if(q1.size()>q2.size())
				printf("%d\n",q1.top());
			else
				printf("%d\n",q2.top());
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值