【备战秋招】每日一题:2022年清华大学(深圳)保研夏令营机试题-第二题-众数

文章描述了一种数据结构问题,需要维护多个序列,执行插入、删除和查询众数的操作。解决方案包括离线处理所有操作,使用栈来处理序列的增删,用值域线段树维护每个数的出现次数和最大值。复杂度为O(qlogq),其中q是操作次数。
摘要由CSDN通过智能技术生成

为了更好的阅读体检,可以查看我的算法学习博客众数

题目内容

有几个序列(依次编号为 0 , 1 , . . . . , n − 1 0,1,....,n - 1 0,1,....,n1 ),初始时各个序列都为空。你的任务是维护这 n n n 个序列,需要进行的各种操作的表示与意义如下:

  • 1   i   k   x 1\ i\ k\ x 1 i k x :在第 i i i 个序列的末尾插入 k k k个值都为 x x x 的数;
  • 2   i   k 2\ i\ k 2 i k :删除第 i i i 个序列末尾的 k k k 个数,若该序列已不足 k k k 个数,则删除序列中全部的数;
  • 3   i 3\ i 3 i :询问第 i i i 个序列的众数。

其中,一个序列的众数定义为该数列中出现次数最多的数,若出现次数最多的数有多种,取其中数值最小的数。

输入描述

输入第一行为两个正整数 n , q n,q n,q ,表示序列的个数与操作次数。

接下来 q q q 行描述依次进行的操作,每行描述一个操作,每个操作的输入方式同题目描述。

对于所有输入数据,均满足 1 ≤ n ≤ 1 0 5 , 1 ≤ q ≤ 1 0 6 1\le n\le 10^5,1\le q\le 10^6 1n1051q106 ,任何出现的序列编号 i i i 都满足 0 ≤ i < n 0\le i\lt n 0i<n

序列中出现的任何数值 x x x 均满足 0 ≤ x ≤ 1 0 9 0\le x\le 10^9 0x109 ,插入和删除操作中的数目 k k k 满足 1 ≤ k ≤ 1 0 9 1\le k \le 10^9 1k109

输出描述

对于每个询问操作, 输出询问时对应序列中出现次数最多的数中数值最小者,并换行。

样例

输入

2 6
1 0 2 1
1 0 3 2
3 0
2 0 1
3 0
3 1

输出

2
1
-1

样例解释

1 1 1 次询问时, 0 0 0 号序列为 1 1 2 2 2 ,唯一众数为 2 2 2

2 2 2 次淘问时, 0 0 0 号序列为 1 1 2 2 ,两种数都出现了 2 2 2 次,取较小的 1 1 1

3 3 3 次询问时, 1 1 1 号序列为空,输出 − 1 -1 1

题目思路

分析

数据结构题:值域线段树 + 离线处理。

1.离线:观察到队列之间的操作是独立的。所以可以先把所有答案先全部读入进来。然后分队列处理

2.维护队列:

​ 1.由于需要维护尾部删除和尾部增加的操作,所以自然选择来维护这样的关系。由于 k k k比较大,栈中存储二元组 ( x , k ) (x , k) (x,k)代表 k k k x x x

​ 2.由于需要求解序列上的众数。所以需要一种数据结构来维护每个数出现的次数以及"次数"们的最大值。可以使用值域线段树或者使用 h a s h hash hash表 配合 红黑树 来维护这个东西。后者直接使用语言库自带的对象可能常数会比较大。

复杂度

O ( q   l o g   q ) O(q\ log\ q) O(q log q)

证明

假设队列 i i i上的操作为 q i q_i qi次 ,那么操作次数为 q i l o g   q i + q i q_ilog\ q_i + q_i qilog qi+qi

前者是权值线段树的开销。后者是栈的开销。

总操作次数为:
∑ i = 1 n q i   l o g   q i , s . t . ∑ i = 1 n q i = q \sum_{i=1}^{n}q_i\ log\ q_i , s.t.\sum_{i=1}^{n} q_i=q i=1nqi log qi,s.t.i=1nqi=q
最差情况:
{ m a x   f ( n , q ) = ∑ i = 1 n q i   l o g   q i s . t . ∑ i = 1 n q i = q \left\{\begin{matrix} max\ f(n,q)=\sum_{i=1}^{n}q_i\ log\ q_i \\ s.t. \sum_{i=1}^{n} q_i=q \end{matrix}\right. {max f(n,q)=i=1nqi log qis.t.i=1nqi=q
根据拉格朗日定理,当 q 1 = q 2 = . . . = q n q_1=q_2=...=q_n q1=q2=...=qn时,目标函数最大化。那么
f m a x ( n , q ) = n ∗ q n   l o g   q n = q ( l o g   q − l o g   n ) = O ( q   l o g   q ) f_{max}(n , q) =n * \frac{q}{n}\ log\ \frac{q}{n}=q(log\ q-log\ n) = O(q\ log\ q) fmax(n,q)=nnq log nq=q(log qlog n)=O(q log q)

代码+解析

C++

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
const int maxq = 1e6 + 6;
#define ll long long
// 线段树部分 开始
struct Node {
    int id , op , k , x;
};
vector<Node> op[maxn];
int ans[maxq];
int dist[maxq] , cnt;
struct TreeNode {
    int l , r;
    ll num;
}st[maxq << 4];
void build (int l , int r , int t){
    if (l > r) return ;
    st[t].l = l;
    st[t].r = r;
    st[t].num = 0;
    if (l == r) return ;
    int mid = l + r >> 1;
    int ls = t << 1 , rs = ls + 1;
    build(l , mid , ls);
    build(mid + 1 , r , rs);
}
void pushup (int t){
    int ls = t << 1 , rs = ls + 1;
    st[t].num = max(st[ls].num , st[rs].num);
}
void add (int t , int pos , ll x){
    if (st[t].l == st[t].r){
        st[t].num += x;
        return ;
    }
    int mid = st[t].l + st[t].r >> 1;
    int ls = t << 1 , rs = ls + 1;
    if (pos <= mid) add(ls , pos , x);
    else add(rs , pos , x);
    pushup(t);
}
// 寻找众数
int findmx (int t){
    if (st[t].l == st[t].r) return st[t].l;
    int ls = t << 1 , rs = ls + 1;
    if (st[ls].num >= st[rs].num) return findmx(ls);
    return findmx(rs);
}
// 线段树部分 结束
int main()
{
    // 读入
    int n , q;
    scanf("%d%d" , &n , &q);
    // 离线存储
    for (int i = 1; i <= q; i++){
        int opid , id , k , x;
        scanf("%d" , &opid);
        if (opid == 1){
            scanf("%d%d%d" , &id , &k , &x);
            op[id].push_back({i , opid , k , x});
        }else if (opid == 2){
            scanf("%d%d" , &id , &k);
            op[id].push_back({i , opid , k , -1});
        }else {
            scanf("%d" , &id);
            op[id].push_back({i , opid , -1 , -1});
        }
    }
    // 按每个队列分别处理
    for (int i = 0 ; i < n ; i++){
        cnt = 0;
        for (auto &x : op[i]){
            if (x.op == 1) dist[++cnt] = x.x;
        }
        // 离散化
        sort(dist + 1 , dist + cnt + 1);
        cnt = unique(dist + 1 , dist + cnt + 1) - dist - 1;
        for (auto & x : op[i])
            x.x = lower_bound(dist + 1 , dist + cnt + 1 , x.x) - dist;
        // 1.因为要增加 和 删除 -> 存队列的顺序结构 -> 所以使用<栈>
        stack<pair<int,int>> s;
        // 2.因为要求众数 -> 存队列的值域结构 -> 所以使用<值域线段树>
        // 需要支持:1.求最小众数 2.添加/删除
        build(1 , cnt , 1);
        for (auto & x : op[i]){
           // cout << "现在处理第" << i << "个队列,操作为" << x.op << " " << x.k << " " << x.x << endl;
            if (x.op == 1){
                add(1 , x.x , x.k);
                s.push({x.x , x.k});
            }else if (x.op == 2){
                int rest = x.k;
                while (s.size() && rest > 0){

                    if (s.top().second <= rest){
                        add(1 , s.top().first , -s.top().second);
                        rest -= s.top().second;
                        s.pop();
                    }else {
                        add(1 , s.top().first , -rest);
                        s.top().second -= rest;
                        rest = 0;
                    }
                }
            }else {
                if (st[1].num == 0) ans[x.id] = -1;
                else {
                    ans[x.id] = dist[findmx(1)];
                }
            }
        }
        build(1 , cnt , 1);
    }
    for (int i = 1 ; i <= q ; i++){
        if (!ans[i]) continue;
        printf("%d\n" , ans[i]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值