动态中位数

题意

题意

题解

注:本文的代码中的输出不严格正确,反正loj的数据也没多强,不会PE。如果后面加强了当我没说

做法1

没错,第一个还是我的做法。

记得是哪年的SCP还是NOIP初赛竟然程序题就是这个思路,然后我照搬了。

我们这道题目反着考虑,它让我们加数,我们就删除数字,从后往前删除数字,我们先建一个排好序的链表,然后从中一个个删除数字,然后通过链表来跳,由于每次最多删除两个数字,除2就是一个,所以链表每次最多跳一次,时间复杂度:\(O(nlogn)\)

当然有一种情况要特殊考虑一下,就是当\(n=2\)时删除到了中位数的位置,这种情况要处理一下,其他也就没什么了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define  N  11000
using  namespace  std;
struct  fuck
{
	int  x,y;
}a[N];
inline  bool  cmp(fuck  x,fuck  y){return  x.x<y.x;}
struct  node
{
	int  l,r,x/*权值*/;
}b[N];
void  del(int  x)
{
	b[b[x].l].r=b[x].r;
	b[b[x].r].l=b[x].l;
}
int  n,be[N];
int  list[N],top;
int  main()
{
	int  T;scanf("%d",&T);
	while(T--)
	{
		top=0;
		int  t;scanf("%d%d",&t,&n);
		for(int  i=1;i<=n;i++)
		{
			scanf("%d",&a[i].x);
			a[i].y=i;
			b[i].l=i-1;b[i].r=i+1;
		}
		sort(a+1,a+n+1,cmp);
		for(int  i=1;i<=n;i++)be[a[i].y]=i,b[i].x=a[i].x;//确定其在链表中的位置 
		int  ans=n/2+1,l=n/2/*左边有多少个数字*/,r=n/2/*右边有多少个数字*/;
		for(int  i=n;i>=1;i--)//按顺序删除 
		{
			if(i%2==1)
			{
				if(l<r)
				{
					ans=b[ans].r;l++;r--;
				}
				else  if(l>r)
				{
					ans=b[ans].l;l--,r++;
				}
				list[++top]=b[ans].x;
			}
			if(be[i]==ans)//刚好卡在中位数的位置
			{
				if(l>r)ans=b[ans].l,l--,r++;
				else  if(l<=r)ans=b[ans].r,l++,r--;
			}
			if(be[i]>ans)r--;
			else  l--;
			del(be[i]);
		}
		//后面就是输出的问题了。
		printf("%d %d\n",t,n/2+1);
		int  now=0;
		for(int  i=top;i>=1;i--)
		{
			now++;
			if(now>10)
			{
				now-=10;
				printf("\n");
			}
			printf("%d",list[i]);
			if(now!=10)printf(" ");
		}
		printf("\n");
	}
	return  0;
}

做法2

https://www.acwing.com/solution/content/838/

搞两个堆,然后不断插入,如果\(size\)之差\(≥2\)了,时间复杂度:\(O(nlogn)\)。(当然,两个堆的做法也可以有其他实现方法(例如像做法1一样,等到奇数再调节,而且最多调节一次),但本质上都是通过两个堆求中间的数字。)

时间复杂度:\(O(nlogn)\)

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
using namespace std;
struct cmp1
{
    bool operator ()(int &a,int &b)
    {
        return a>b;//小根堆,不是大根堆
    }
};
priority_queue <int,vector<int>, cmp1> q1,kong1;
priority_queue <int> q2,kong2;
void init()
{
    int t,x,n,now;
    cin>>t;
    while(t--)
    {
        cin>>x>>n;
        cout<<x<<" "<<(n+1)/2<<endl;
        q1=kong1;
        q2=kong2;
        int cnt=0;
        for (int i=1;i<=n;i++)
        {
            cin>>now;
            if(q1.empty())
                q1.push(now);
            else
            {
                if(now>q1.top()) 
                    q1.push(now);
                else 
                    q2.push(now);
                while(q1.size()<q2.size())
                {
                    q1.push(q2.top());
                    q2.pop();
                }
                while(q1.size()>q2.size()+1)
                {
                    q2.push(q1.top());
                    q1.pop();
                }
            }
            if (i&1)
            {
                cnt++;
                cout<<q1.top()<<" ";
                if (!(cnt%10))
                    cout<<endl;
            }
        }
        puts("");
    }
}
int main()
{
    init();
    return 0;
}


作者:秦淮岸灯火阑珊已退役
链接:https://www.acwing.com/solution/content/838/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

做法3

我们考虑用线段树来找到中位数是哪个,对于第\(i\)次而言,我们就是要找到\(\frac{i}{2}+1\)大的数组,那么能不能让这个这么大的数字自动找上门来呢?

我们先让\(t[i]=\frac{i}{2}+1\)

我们排序一遍,然后从小到大看,如果这个数字\(x\)原本就是在第\(i\)次及之前插入的,我们就把\(t[i]--\),如果\(t[i]=0\)了,那么这个就是中位数。

不难发现,如果一个数字是第\(i\)次插入的话,那么\(t[i]\)~\(t[n]\)都要减一。

那么我们只要用线段树管理\(t\)数组不就行了吗?

但是怎么处理\(t[i]=0\)的情况呢?

我们暴力储存正数最小值,当正数最小值等于\(0\)时就暴力进入这个区域重复此操作,把为\(0\)的数拿出来同时更新最小值。

时间复杂度:对于管理\([l,r]\)区间的节点,他最多有\(r-l+1\)次进入机会,所以全部节点的范围加起来,就是\(O(nlogn)\)啦。

当然,这也能解决每一次插入求的不是中位数,而是第\(k\)大的题目。

当然,由于这道题目只是当奇数的时候再查询,所以可以处理一下减少常数,嫌麻烦也可以直接全部偶数设为inf,这样也没有多大问题。

没有代码。

做法4

平衡树他不香吗。(求\(\frac{i}{2}+1\)大值)

时间复杂度:\(O(nlogn)\)

posted @ 2020-07-31 10:59  敌敌畏58  阅读(235)  评论(0编辑  收藏  举报