【中位数】对顶堆维护

基本思路

    让大根堆的元素<=mid
    让小根堆的元素 > mid 

显然如果个数是奇数个,大根堆数量要比小根堆多1

否则取出两堆堆顶元素求平均数

P1168 中位数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

板子题

#include<bits/stdc++.h>

using ll = long long;
using ull = unsigned long long;
using ari = std::array<int, 3>;
using PII = std::pair<int, int>;

const int N = 2e5 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-6;
#define int ll
int n,k;
int a[N];
void solve() {
	std::cin>>n;
	std::priority_queue<int> d;//默认大根堆 
	std::priority_queue<int,std::vector<int>,std::greater<int> > s;
	
//	让大根堆的元素<=mid
//	让小根堆的元素>mid 
	int mid=INT_MIN;
	for(int i=1;i<=n;i++)
	{
		std::cin>>a[i];
		
		if(i==1) mid=a[1];
		if(a[i]<=mid)
		{
			d.push(a[i]); 
		}else s.push(a[i]);
		
		if(i%2)
		{
			while((int)d.size()-(int)s.size()<1)
			{
				d.push(s.top());
				s.pop();
			}
			while((int)d.size()-(int)s.size()>1)
			{
				s.push(d.top());
				d.pop();
			}
			mid=d.top();
			std::cout<<mid<<'\n';
		}
	} 
}
signed main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);

    int t = 1;
    //std::cin>>t;
    while (t--) {
        solve();
    }

    return 0;
}

295. 数据流的中位数 - 力扣(LeetCode)

对顶堆 

对奇数个偶数个都求中位数 

class MedianFinder {
public:
    std::priority_queue<int> l;//默认大根堆 
	std::priority_queue<int,std::vector<int>,std::greater<int> > r;

    MedianFinder() {

    }
    
    void addNum(int num) {
        double mid=findMedian();
        if(mid>100000||num<=mid) l.push(num);
        else r.push(num);

        int sum=(int)l.size()+(int)r.size();
        if(sum%2)
        {
            while((int)l.size()-(int)r.size()<1){
                l.push(r.top());
                r.pop();
            }
            while((int)l.size()-(int)r.size()>1){
                r.push(l.top());
                l.pop();
            }
        }else{
            while((int)l.size()-(int)r.size()<0){
                l.push(r.top());
                r.pop();
            }
            while((int)l.size()-(int)r.size()>0){
                r.push(l.top());
                l.pop();
            }
        }
    }
    
    double findMedian() {
        if(l.size()==0) return 1000000;
        if((int)l.size()>(int)r.size()) return l.top();
        else return (l.top()+r.top())/2.0;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

 有序集合+双指针

第一次用这个数据结构感觉还蛮好用的。

set、multiset、map、multimap
特点:底层实现是红黑树,键值有序,set 和 map 键不可重复,而 multiset 和 multimap 可重复;
复杂度:插入、删除、查找都为O(logN);

感觉就是自动实现一个有序数组,通过左右两个迭代器来维护中位数的位置。

如果插入之后只有一个元素,毫无疑问,起始迭代器就是中位数。

如果插完是奇数,说明原来是偶数,l和r分别指向两个数字,现在要让他们指向一个数字。

        如果要插入的数字刚好>=l且<r,说明左指针要往右边移动,右指针要往左边移动。

        如果要插入的数字刚好<l,说明右指针要往左边移动。

        如果要插入的数字刚好>=r,说明左指针要往右边移动。

如果插完是偶数,说明原来是奇数,l和r指向同一个数字,现在要让他们分别指向两个数字。

        如果当前数字<l,左指针左移

        如果当前数字>=l,右指针右移

class MedianFinder {
    std::multiset<int> nums;
    std::multiset<int>::iterator l,r;

public:
    MedianFinder() {
        l=nums.end();
        r=nums.end();
    }
    
    void addNum(int num) {
        nums.insert(num);
        int n=nums.size();

        if(n==1){
            l=r=nums.begin();
        }else if(n%2){
            //原来是偶数,最后需要指向一个数
            if(num>=*l&&num<*r){
                l++,r--;
            }else if(num<*l){
                r--;
            }else l++;
        }else{
            if(num<*l){
                l--;
            }else r++;
        }
    }
    
    double findMedian() {
        return (*l+*r)/2.0;
    }
};

/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder* obj = new MedianFinder();
 * obj->addNum(num);
 * double param_2 = obj->findMedian();
 */

 480. 滑动窗口中位数 - 力扣(LeetCode)

C++ STL prev()和next()函数(深入了解,一文学会)-CSDN博客好文!

class Solution {
public:
    vector<double> medianSlidingWindow(vector<int>& nums, int k) {
        std::multiset<int> s(nums.begin(),nums.begin()+k);
        int len=nums.size();

        int l=0,r=k-1;
        auto mid=next(s.begin(),k/2);//从起点往后移动k/2,最后在k/2+1

        std::vector<double> ans;
        while(r<len)
        {
            double midd=( (double)(*mid) + (double)(*prev(mid,1-k%2)) ) /2;
            ans.push_back(midd);

            if(r+1==len) break;

            s.insert(nums[++r]);
            if(nums[r]<*mid) mid--;
            if(nums[l]<=*mid) mid++;

            s.erase(s.find(nums[l++]));
        }
        return ans;
    }
};

Problem - C - Codeforces

k次操作,选择i bi=1,ai++
让max(ai+mid(ci))最大,直接对数组排序,特判中位数前后的中位数

观察样例发现会有特例,特例出现在中位数发生改变的情况,因此二分可能出现的最大中位数。

#include<bits/stdc++.h>

using ll = long long;
using ull = unsigned long long;
using ari = std::array<int, 3>;
using PII = std::pair<int, int>;

const int N = 2e5 + 10;
const int mod = 1e9 + 7;
const double eps = 1e-6;

#define int ll
int n,k;
int c[N];
int tag[N];
struct node{
    int a,b;
}d[N];
bool cmp(node a,node b){
    return a.a<b.a;
}
void solve() {
    std::cin>>n>>k;
    for(int i=1;i<=n;i++)
    {
        std::cin>>d[i].a;
    }
    //k次,选择i bi=1,ai++
    //让max(ai+mid(ci))最大
    for(int i=1;i<=n;i++)
    {
        std::cin>>d[i].b;
    }
    std::sort(d+1,d+1+n,cmp);

    int mid=(n+1)/2;
    //1 2 3 122
    //1 2 3 4 5
    if(n%2){
        //删完变偶数
        for(int i=1;i<=n;i++)
        {
            if(i<mid){
                c[i]=d[mid].a;
                tag[i]=mid;
            }else{
                c[i]=d[mid-1].a;
                tag[i]=mid-1;
            }
        }

    }else{
        //1 2 3 4
        for(int i=1;i<=n;i++)
        {
            if(i<=mid){
                c[i]=d[mid+1].a;
                tag[i]=mid+1;
            }else{
                c[i]=d[mid].a;
                tag[i]=mid;
            }
        }
    }
    ll maxn=0;
    for(int i=1;i<=n;i++)
    {
        if(d[i].b!=0)
        {
            maxn=std::max(maxn,d[i].a+c[i]+k);
        }
        maxn=std::max(maxn,d[i].a+c[i]);
    }

    auto check=[&] (int x) ->bool{//判断最大中位数
        std::vector<int> v;
        int tag;
        for(int i=n-1;i>=1;i--)
        {
            if(d[i].a>=x) v.push_back(i);
            else
            {
                tag=i;
                break;
            }
        }
        int num=n-1;
        if(v.size()>=num/2+1) return 1;

        ll cnt=k;
        for(int i=tag;i>=1;i--){
            if(d[i].b==0) continue;

            int q=x-d[i].a;
            if(cnt>=q) {
                cnt-=q;
                v.push_back(i);
            }else break;
        }
        return v.size()>=(num/2+1);
    };

    ll l=0,r=INT_MAX,res=-1;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(check(mid)){
            res=mid;
            l=mid+1;
        }else r=mid-1;
    }
    maxn=std::max(maxn,d[n].a+res);
    std::cout<<maxn<<'\n';
}
//4 4
//2 1 5 1
//0 1 0 1
//
//1 1 2 5
//1 1 0 0
//2 2 1 1

//1 1 2 5
//2 3 3 5

//5 2
//10 11 4 10 15
//1 1 0 1 0
//
//26
//4 10 10 11 15
//1  1  1  1  0

signed main() {
    std::ios::sync_with_stdio(0);
    std::cin.tie(0);

    int t = 1;
    std::cin>>t;
    while (t--) {
        solve();
    }

    return 0;
}

  • 17
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值