• 【uoj#280】[UTR #2]题目难度提升 对顶堆+STL-set


    题目描述

    给出 $n$ 个数 $a_1,a_2,...,a_n$ ,将其排为序列 ${p_i}$ ,满足 ${前 i 个数的中位数}$ 单调不降。求字典序最大的 ${p_i}$ 。

    其中,对于一个长度为 $m$ 的数列,若 $m$ 为奇数,则中位数为从小到大第 $lceilfrac m2 ceil$ 大的数;若 $m$ 为偶数,则中位数为从小到大第 $frac m2$ 大和第 $frac m2+1$ 大的数的平均值。


    题解

    对顶堆+STL-set

    显然如果已经知道了这个数列的一部分,剩下的一定是每次加入大于等于中位数的数。

    那么如何确定这一“部分呢”?将 $a$ 从小到大排序,然后:

    • 如果 $a_{lceilfrac n2 ceil}=a_{lceilfrac n2 ceil+1}$ ,则可以让任何时刻中位数都等于 $a_{lceilfrac n2 ceil}$ ,找到最大的 $k$ 使得 $a_k+1=a_{lceilfrac n2 ceil}$ ,按照 $k,k+1,k-1,k+2,k-2,...$ 的顺序选择完整个数列即可得到最优解,显然任何时刻中位数都相等。没有考虑到这种情况可以得到60分。
    • 否则如果存在 $k<lceilfrac n2 ceil$ 且 $a_k=a_{k+1}$ ,则按照 $k,k+1,k-1,k+2,k-2,...$ 的顺序选择,直到前面没有数可以取,这个过程中位数都相等。没有考虑到这种情况只能得到所有数互不相同的40分。
    • 否则选择第一个数。

    然后使用multiset保证每次删除后最小的数大于等于中位数,使用对顶堆维护中位数即可。

    对顶堆:使用大根堆维护较小数,使用小根堆维护大数,保证两个堆的大小差不超过1。显然中位数可以直接从两个堆的堆顶元素得到。

    时间复杂度 $O(nlog n)$ 。

    #include <set>
    #include <queue>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #define N 100010
    using namespace std;
    multiset<int> s;
    priority_queue<int> A , B;
    int a[N] , v[N];
    inline void push(int x)
    {
    	if(A.empty() || x <= A.top()) A.push(x);
    	else B.push(-x);
    	if(A.size() < B.size()) A.push(-B.top()) , B.pop();
    	if(A.size() - B.size() > 1) B.push(-A.top()) , A.pop();
    }
    int main()
    {
    	int n , i , p , q , mid;
    	scanf("%d" , &n);
    	for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &a[i]);
    	sort(a + 1 , a + n + 1) , mid = (n + 1) >> 1;
    	if(a[mid] == a[mid + 1])
    	{
    		while(mid < n && a[mid] == a[mid + 1]) mid ++ ;
    		printf("%d" , a[mid]) , p = mid - 1 , q = n;
    		while(p || q > mid)
    		{
    			if(p) printf(" %d" , a[p -- ]);
    			if(q > mid) printf(" %d" , a[q -- ]);
    		}
    		return 0;
    	}
    	while(mid > 1 && a[mid] != a[mid - 1]) mid -- ;
    	printf("%d" , a[mid]) , v[mid] = 1 , p = mid - 1 , q = n;
    	while(p && q > mid) printf(" %d" , a[p]) , v[p -- ] = 1 , printf(" %d" , a[q]) , v[q -- ] = 1;
    	for(i = 1 ; i <= n ; i ++ )
    	{
    		if(v[i]) push(a[i]);
    		else s.insert(a[i]);
    	}
    	while(!s.empty())
    	{
    		p = *s.begin();
    		if(A.size() == B.size())
    		{
    			if(p >= -B.top()) q = *--s.end();
    			else q = *s.begin();
    		}
    		else
    		{
    			if(!B.empty() && p * 2 >= A.top() - B.top()) q = *--s.end();
    			else q = *--s.upper_bound(p * 2 - A.top());
    		}
    		printf(" %d" , q) , s.erase(s.find(q)) , push(q);
    	}
    	return 0;
    }
    
  • 相关阅读:
    动手学深度学习 | 使用和购买GPU | 15
    工作生活及未来畅想 —— 杂谈
    线段树 乌鸦喝水
    测试开发进阶——常用中间件概念——JNDI树理解(转载)
    测试开发进阶——常用中间件概念——JMX监听器理解
    测试开发进阶——常用中间件概念——JMS(Java消息服务)入门&基于Tomcat + JNDI + ActiveMQ实现JMS的点对点消息传送理解(转载)
    测试开发进阶——Servlet ——Servlet异常处理
    测试开发进阶——Servlet ——Servlet发送电子邮件
    测试开发进阶——Servlet ——Servlet上传文件到服务器
    测试开发进阶——Servlet ——Servlet通过JDBC进行数据库访问
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/8681464.html
Copyright © 2020-2023  润新知