中位数相关

中位数的查询方式:

1.对顶堆动态维护

2.主席树--区间K小

3.二分答案 >=mid 染成1,<mid染成-1,求和>=0(这个求和要看具体题目中对中位数的定义)

主席树---G. middle

暴力1 TLE 5

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2e4 + 7;

int n, q[6], ans, a[maxn], b[maxn], cnt, Q, root[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct seg 
{
    struct node 
    {
        int sum;
    }t[maxn<<5];
    int lc[maxn<<5], rc[maxn<<5], tot;
    void build(int &x, int l, int r)
    {
        x = ++tot;
        if(l == r) return;
        int mid = (l + r) >> 1;
        build(lc[x], l, mid);
        build(rc[x], mid+1, r);
    }
    void update(int &x, int pre, int l, int r, int pos)
    {
        x = ++tot; lc[x] = lc[pre], rc[x] = rc[pre];
        t[x].sum = t[pre].sum + 1;
        if(l == r) return;
        int mid = (l + r) >> 1;
        if(pos <= mid) update(lc[x], lc[pre], l, mid, pos);
        else update(rc[x], rc[pre], mid+1, r, pos);
    }
    int query(int x, int pre, int l, int r, int L, int R, int k)
    {
        if(l == r) return b[l];
        int mid = (l + r) >> 1;
        int lsize = t[lc[x]].sum - t[lc[pre]].sum;
        if(lsize >= k) return query(lc[x], lc[pre], l, mid, L, R, k);
        else return query(rc[x], rc[pre], mid+1, r, L, R, k-lsize);
    }
}t;

int main()
{
    n = read(); 
    for(int i=1; i<=n; i++) 
    {
        a[i] = read(); b[i] = a[i];
    }
    sort(b+1, b+1+n);
    cnt = unique(b+1, b+1+n)-b-1;
    t.build(root[0], 1, cnt);
    for(int i=1; i<=n; i++)
    {
        int x = lower_bound(b+1, b+1+cnt, a[i])-b;
        t.update(root[i], root[i-1], 1, cnt, x);
    }
    Q = read();
    while(Q--)
    {
        q[0] = read(); q[1] = read(); q[2] = read(); q[3] = read();
        q[0] = (q[0] + ans) % n + 1, q[1] = (q[1] + ans) % n + 1;
        q[2] = (q[2] + ans) % n + 1; q[3] = (q[3] + ans) % n + 1;
        sort(q, q+4);
        //l = (q[0] + q[3]) / 2; r = ()
        ans = 0;
        //printf("%d %d %d %d\n", q[0], q[1], q[2], q[3]);
        for(int i=q[0]; i<=q[1]; i++)
        {
            for(int j=q[2]; j<=q[3]; j++)
            {
                int k = (j - i + 1) / 2 + 1;
                //printf("i = %d j = %d k = %d\n", i, j, k);
                int w = t.query(root[j], root[i-1], 1, n, i, j, k);
                //printf("w = %d\n", w);
                ans = max(ans, w);
            }
        }
        printf("%d\n", ans);
    }

    return 0;
}
用K小数求每个中位数

暴力2 TLE 15,不知道为什么别人TLE 20,难道是常数?

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 2007;

int ans, A[maxn], n, mid[maxn][maxn], q[6], Q;
priority_queue<int> a;
priority_queue<int, vector<int>, greater<int> > b;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

void Push(int x)
{
    if(a.empty() || x<=a.top()) a.push(x);
    else b.push(x);
    while(a.size() < b.size() + 1)
    {
        a.push(b.top()); b.pop();
    }
    while(a.size() > b.size())
    {
        b.push(a.top()); a.pop();
    }
}

int main()
{
    n = read(); 
    for(int i=1; i<=n; i++) A[i] = read();
    for(int i=1; i<=n; i++)
    {
        while(!a.empty()) a.pop();
        while(!b.empty()) b.pop();
        for(int j=i; j<=n; j++)
        {
            Push(A[j]);
            mid[i][j] = b.top();
        }
    }
    Q = read();
    while(Q--)
    {
        int a = read(), b = read(), c = read(), d = read();
        a = (a+ans)%n+1, b = (b+ans)%n+1, c = (c+ans)%n+1, d = (d+ans)%n+1;
        q[0] = a, q[1] = b, q[2] = c; q[3] = d;
        sort(q, q+4);
        a = q[0], b = q[1], c = q[2], d = q[3];
        ans = 0;
        for(int i=a; i<=b; i++)
        {
            for(int j=c; j<=d; j++)
            {
                ans = max(ans, mid[i][j]);
            }
        }
        printf("%d\n", ans);
    }

    return 0;
}
对顶堆+预处理

正解 找到有可能成为中位数的最大的mid,为了让它尽量满足条件,把[a, b]的最大后缀,(b, c)的区间和,[c, d]的最大前缀加起来判断。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 5e5 + 3;

int n, g[maxn], v[maxn], m, ans, root[maxn], q[6];
int a, b, c, d;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

inline bool cmp(int x, int y) {return g[x] < g[y];}

struct seg 
{
    struct node 
    {
        int sum, pre, suf;
    }t[maxn<<5];
    int lc[maxn<<5], rc[maxn<<5], tot;
    inline void pushup(int x)
    {
        t[x].sum = t[lc[x]].sum + t[rc[x]].sum;
        t[x].pre = max(t[lc[x]].pre, t[lc[x]].sum + t[rc[x]].pre);
        t[x].suf = max(t[rc[x]].suf, t[rc[x]].sum + t[lc[x]].suf);
    }
    void build(int &x, int l, int r)
    {
        x = ++tot;
        if(l == r) 
        {
            t[x].sum = t[x].pre = t[x].suf = -1; return;
        }
        int mid = (l + r) >> 1;
        build(lc[x], l, mid);
        build(rc[x], mid+1, r);
        pushup(x);
    }
    /*void update(int &x, int pre, int l, int r, int pos)
    {
        if(!x) x = ++tot;
        if(l == r)
        {
            t[x].sum = t[x].pre = t[x].suf = 1; return;
        }
        int mid = (l + r) >> 1;
        if(pos <= mid) rc[x] = rc[pre], update(lc[x], lc[pre], l, mid, pos);
        else lc[x] = lc[pre], update(rc[x], rc[pre], mid+1, r, pos);
        pushup(x);
    }*/
    void update(int &x, int pre, int l, int r, int pos)
    {
        x = ++tot; lc[x] = lc[pre], rc[x] = rc[pre];
        if(l == r)
        {
            t[x].sum = t[x].pre = t[x].suf = 1; return;
        }
        int mid = (l + r) >> 1;
        if(pos <= mid) update(lc[x], lc[pre], l, mid, pos);
        else update(rc[x], rc[pre], mid+1, r, pos);
        pushup(x);
    }
    node query(int x, int l, int r, int L, int R)
    {
        if(L <= l && r <= R) return t[x];
        int mid = (l + r) >> 1;
        if(L > mid) return query(rc[x], mid+1, r, L, R);
        else if(R <= mid) return query(lc[x], l, mid, L, R);
        else 
        {
            node ans, ls = query(lc[x], l, mid, L, R), rs = query(rc[x], mid+1, r, L, R);
            ans.sum = ls.sum + rs.sum;
            ans.pre = max(ls.pre, ls.sum + rs.pre);
            ans.suf = max(rs.suf, rs.sum + ls.suf);
            return ans;
        }
    }
}t;

inline bool check(int mid)
{
    int res = 0; seg::node ans;
    ans = t.query(root[mid], 1, n, a, b); res += ans.suf;
    if(b+1 <= c-1) ans = t.query(root[mid], 1, n, b+1, c-1), res += ans.sum;
    ans = t.query(root[mid], 1, n, c, d); res += ans.pre;
    return res >= 0;
}

int main()
{
    n = read();
    for(int i=1; i<=n; i++)
    {
        g[i] = read(); v[i] = i;
    }
    sort(v+1, v+1+n, cmp);
    t.build(root[n+1], 1, n);
    for(int i=n; i; i--) t.update(root[i], root[i+1], 1, n, v[i]);
    m = read();
    while(m--)
    {
        a = read(), b = read(), c = read(), d = read();
        a = (a + ans) % n + 1, b = (b + ans) % n + 1;
        c = (c + ans) % n + 1, d = (d + ans) % n + 1;
        q[1] = a, q[2] = b, q[3] = c, q[4] = d;
        sort(q+1, q+1+4);
        a = q[1], b = q[2], c = q[3], d = q[4];
        int l = 1, r = n;
        while(l < r)
        {
            int mid = (l + r + 1) >> 1;
            if(check(mid)) l = mid;
            else r = mid - 1;
        }
        ans = g[v[l]];
        printf("%d\n", ans);
    }

    return 0;
}
两版update都是A的

 

来自学长的推荐---D. 题目难度提升

https://blog.csdn.net/qq_36797743/article/details/83303549

1.没有重复数字时,先填入最小的,因为选择的第一个数会作为中位数,那么第二个就一定要比它大才能使中位数单调不降,所以中位数变得更大了,如果第三个数比第一个还小,那中位数回到第一个它不合法了所以中位数又变大了……类推下去发现比第一个数还小的数一定放不进去,所以第一个数一定要放最小的。

第一个数放完了,后面的数就都可以贪心解决了——每次都在没填的数中选尽可能大的一个,满足把它填进去之后,剩下的数里最小的数不比中位数小(没有重复数字时,新加入的数比中位数大就使中位数变大,反之就使中位数变小)。具体的实现方法就是用对顶堆来维护一下中位数并且用multiset维护一下未填数集(开multiset是为了下一种可以重复的情况)。

https://blog.csdn.net/chenyume/article/details/89045255 这里有关于对顶堆的一些解释,不过在这篇题解中用法和介绍里有些不同,a是一个从大到小排列的堆,里面放的是(奇数)1~n/2+1/(偶数)1~n/2的值,b就是剩下的从小到大排列,如果它维护的区间有奇数个元素,中位数就是a.top(),如果是偶数中位数就是(a.top()+b.top())/2,为了让b小的当top可以把b存相反数,中位数就变成(a.top()-b.top())/2。

现在最小的数已经填上了,考虑剩下的:如果已填数集合中有偶数个数,当前中位数是a.top()和b.top()的平均数,填入一个比中位数更大的数(不可避免)后中位数向大偏移,这时有两种情况,要么变成b.top(),要么变成NewOne(b.top()>NewOne)……(详见代码里的注释)。如果已填数集合中有奇数个数,新加入一个数后的中位数也是有两种情况,要么变成(a.top()+b.top())/2,要么变成(a.top()+NewOne)/2(NewOne<b.top())显然它的值比上一种情况小,如果最小的未填数比最大可能的新中位数还大,那就随便填吧,否则就需要x>=(a.top()+NewOne)/2,移项可得NewOne<=2*x-a.top(),其中NewOne是小于等于里最大的,所以用upper_bound-1,它的最坏情况是x。

2.有重复数字时,先考虑重复数字。在可重情况下有一个非常好用的规律——如果中间有两个相等的数,那就让他们一直作为中位数一小一大地往后填,每次选的恰好就是当前能填入的合法数中最大的一个,这个结论还可以推广一下——又有两种情况:如果重复的数在中点之后(当然是排过序的),因为最后的中位数一定会小于它,所以它永远不会成为中位数,推广失败,把它看成不相等的就好了;如果在中点之前,结论就又可以应用了,让重复的哪些数成为前半段的中位数就好(这里的前半段不是指区间,而是指时间,这个一半也不是准确的一半,只能说是一段,因为它的值偏小,填完这一段后中位数一定会向上偏移),预处理完相等的这些情况之后,就可以和不相等的情况一样考虑了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 1e5 + 2;
const ll mod = 1e9 + 7;
const int INF = 2147483647;
const int lim = 1e4 + 1;

int A[maxn], n;
bool ok[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

priority_queue<int> a, b;

void Push(int x)
{
    if(a.empty() || x<=a.top()) a.push(x);//a : da->xiao
    else b.push(-x);//b: xiao->da
    if(a.size()<b.size()) 
    {
        int x = b.top(); b.pop(); a.push(-x);//a b 的相反由定义决定
    }
    if(a.size()>b.size()+1)
    {
        int x = a.top(); a.pop(); b.push(-x);
    }
}

multiset<int> s;
multiset<int>::iterator it;

int main()
{
    freopen("d.in", "r", stdin);
    freopen("d.out", "w", stdout);
    
    n = read();
    for(int i=1; i<=n; i++)
    {
        scanf("%d", &A[i]);
    }
    sort(A+1, A+1+n);
    int mid = (n + 1) >> 1;
    if(A[mid] == A[mid+1])
    {
        while(A[mid] == A[mid+1]) mid++;
        printf("%d ", A[mid]);
        int p = n, q = mid - 1;
        while(p>mid || q>0)
        {
            if(q>0) printf("%d ", A[q--]);
            if(p>mid) printf("%d ", A[p--]);
        }
        exit(0);
    }
    while(mid>1 && A[mid-1]!=A[mid]) mid--;
    ok[mid] = 1;
    printf("%d ", A[mid]);
    int p=n, q=mid-1;
    while(p>mid && q>0)
    {
        ok[q] = 1; printf("%d ", A[q--]);
        ok[p] = 1; printf("%d ", A[p--]);
    }
    for(int i=1; i<=n; i++)
    {
        if(ok[i]) Push(A[i]);//已经填入的数,用两个堆来维护中位数
        else s.insert(A[i]);//没填的数,在每一位填之前,寻找一个我们能填的最大的数,
        //且满足填了以后,最小的数还是不比中位数小
    }
    while(!s.empty())
    {
        int x = *s.begin();//未填入的数中最小的那个
        int ans;
        //事实上未填数集合中的所有数都比当前中位数大,所以以下if如果不被满足就会不合法
        if(a.size() == b.size())//b.top()是填入一个中位数比当前中位数大的数后,中位数会变成的最大可能
        {
            if(x>=(-b.top())) ans = *(--s.end());//如果它的影响不足以使最小的未填数填不进去,直接从未填的数里选最大的
            else ans = x;
        }
        else 
        {
            if(!b.empty()&&(x*2>=a.top()-b.top())) ans = *(--s.end());
            else 
            {
                ans = *(--s.upper_bound(x*2-a.top()));
            }
        }
        printf("%d ", ans);
        Push(ans); s.erase(s.find(ans));
    }
    
    return 0;
}
View Code

 

posted @ 2022-08-05 21:58  Catherine_leah  阅读(36)  评论(0编辑  收藏  举报
/* */