zoukankan      html  css  js  c++  java
  • 利用两个堆来维护第K大之Poj3784 Running Median

    堆一般用来查找最大值,但利用两个堆,也可以用来维护一些特殊的第K大值。

    例如:
    动态维护中位数问题:依次读入一个整数序列,每当已经读入的整数个数为奇数时,输出已读入的整数构成的序列的中位数。
    输入
    The first line of input contains a single integer P, (1 ≤ P ≤ 1000), which is the number of data sets that follow.
    The first line of each data set contains the data set number,
    followed by a space, followed by an odd decimal integer M, (1 ≤ M ≤ 9999),
    giving the total number of signed integers to be processed. The remaining line(s) in the dataset consists of the values, 10 per line, separated by a single space.
    The last line in the dataset may contain less than 10 values.
    输出
    For each data set the first line of output contains the data set number, a single space and
    the number of medians output (which should be one-half the number of input values plus one).
    The output medians will be on the following lines, 10 per line separated by a single space.
    The last line may have less than 10 elements, but at least 1 element. There should be no blank lines in the output.
    样例输入
    3
    1 9
    1 2 3 4 5 6 7 8 9
    2 9
    9 8 7 6 5 4 3 2 1
    3 23
    23 41 13 22 -3 24 -31 -11 -8 -7
    3 5 103 211 -311 -45 -67 -73 -81 -99
    -33 24 56
    样例输出
    1 5
    1 2 3 4 5
    2 5
    9 8 7 6 5
    3 12
    23 23 22 22 13 3 5 5 3 -3
    -7 -3

    Sol:思考一下中位数x的定义,N为奇数时,有N/2个数字比x小,另N/2个数字比x大。

    于是可以将从小到大的N/2个数字,放入一个堆q1

    将后面的N/2个数字连同x,放入一个堆q2。

    为了满足我们开始的设定,我们可以让q1为大根堆,q2为小根堆。

    于是当大根堆的堆顶值都比小根堆的堆顶值小,则小根堆的堆顶值就是我们要求的。

    再来考虑后面的加元素操作,明显可以一次读入两个数字,将较大值放入小根堆,较小值放入大根堆。

    再检查下大根堆的堆顶值是否比小根堆的堆顶值小。如果不是则交换下两者的堆顶值。

    例如开始放入的是1,3,5.

    则大堆根中为1,小根堆中为3,5.结果为3

    再加入9,10后。

    则大根堆为1 9,小根堆为3 5 10,明显不满足开始的设定,于是交换堆顶得到

    大根堆为1 3,小根堆为 5 9 10

    结果为5.

    #include <cstdio>
    #include <queue>
    using namespace std;
    priority_queue<int> maxq; //大根堆 
    priority_queue<int,vector<int>,greater<int> >minq; //小根堆 
    int n,m,a[100005];
    
    void swap(int x,int y)
    {
        x^=y;
        y^=x;
        x^=y;
    }
    
    int main()
    {
    //  freopen("1.in","r",stdin);
        int T;
        scanf("%d", &T);
        while (T--)
        {
            scanf("%d%d",&m,&n);
            printf("%d %d
    ",m,(n+1)/2);
            int x;
            scanf("%d",&x);
            a[1]=x;
            while (!minq.empty()) minq.pop();
            while (!maxq.empty()) maxq.pop();
            minq.push(x); 
            //因为规定将结果放在小根堆的堆顶,所以第一个数字放入小根堆 
            int cnt=1;
            for (int i=3;i<=n;i+=2)
            {
                int y;
                scanf("%d%d",&x,&y);
                if (x<y) swap(x,y); 
                //一次读两个数字,将较大的给x,较小的给y 
                minq.push(x); //较大值放在小根堆中,因为小根堆存的是数值较大的数字 
                maxq.push(y);//较小值放在大根堆中,因为大根堆存的是数值较小的数字
                int u=minq.top();
                int v=maxq.top();
                if (u<v)
                //保证小根堆的堆顶元素值要大于大根堆的堆顶元素 
                {
                    minq.pop();
                    maxq.pop();
                    minq.push(v);
                    maxq.push(u);
                }
                a[++cnt]=minq.top();
            }
            for (int i=1;i<=cnt;i++)
                if (i%10==0||i==cnt) printf("%d
    ",a[i]);
                    else printf("%d ",a[i]);
        }
    }  

    研究了一下,发现并不需要这么复杂。

    其实只要将读入的每个数字都加入小根堆(用来选小数字用的)

    一旦发现小根堆的元素个数比大根堆的多了2个,那就分一个堆顶值给大根堆。

    由于可能前期给大根堆的数字过大,于是为了保证小根堆的堆顶大于大根堆的根顶,我们还要看下是否满足这个条件。

    如果不满足,那就交换堆顶元素值就好了。当然这时要注意大根堆是否为空。

    #include<bits/stdc++.h>
    using namespace std;
    int a[10010],num[10010];
    //至多有10000个元素,所以开到10000去,开始num数组开小了,re了几次。 
    priority_queue<int> q2;
    priority_queue<int,vector<int>,greater<int> > q1;
    
    int main()
    {
        int T,cas,m,x;
        scanf("%d",&T);
        while(T--)
        {
            while(!q1.empty()) q1.pop();
            while(!q2.empty()) q2.pop();
            int c = 0;
            memset(a,0,sizeof(a));
            scanf("%d%d",&cas,&m);
            for (int i=1;i<=m;i++)
                 cin>>num[i];
            for(int i=1;i<=m;i++)
            {
                int x=num[i];
                q1.push(x);
                if (q1.size()-q2.size()>=2)
                {
                    int t = q1.top();
                    q1.pop();
                    q2.push(t);
                }
                if (q2.size()!=0) //这里要注意大根堆是否为空 
                {
                   
                int u=q1.top();
                int v=q2.top();
                if (u<v)
                //保证小根堆的堆顶元素值要大于大根堆的堆顶元素
                {
                    q1.pop();
                    q2.pop();
                    q1.push(v);
                    q2.push(u);
                }
                }
                if(i%2==1)
                {
                	c++;
                    a[c] = q1.top();
                }
            }
            printf("%d %d
    ",cas,m/2+1);
            for (int i=1;i<=c;i++)
                if (i%10==0||i==c) printf("%d
    ",a[i]);
                    else printf("%d ",a[i]);
        }
        return 0;
    }
    

      

    类似的习题还有black box,也可以看一下。一种特殊的求第K大的题。

  • 相关阅读:
    Notes about "Exploring Expect"
    Reuse Sonar Checkstyle Violation Report for Custom Data Analysis
    Eclipse带参数调试的方法
    MIT Scheme Development on Ubuntu
    Manage Historical Snapshots in Sonarqube
    U盘自动弹出脚本
    hg的常用配置
    Java程序员的推荐阅读书籍
    使用shared memory 计算矩阵乘法 (其实并没有加速多少)
    CUDA 笔记
  • 原文地址:https://www.cnblogs.com/cutemush/p/13771640.html
Copyright © 2011-2022 走看看