Max - Min Query -from NOMURA Programming Contest 2022(AtCoder Beginner Contest 253)

本人数据结构小白,感谢大家的支持!

目录

Problem Statement

​分析及代码实现

python brute-force

python-dictionary

stl-map

stl-multiset

 变式题目(multiset/priority_queue)


来自AtCoder Beginner Contest 253的C题

C - Max - Min Query

Time Limit: 2 sec / Memory Limit: 1024 MB

Problem Statement

We have a multiset of integers S, which is initially empty.

Given Q queries, process them in order. Each query is of one of the following types.

  • 1 x: Insert an x into S

  • 2 x c: Remove an x from S m times, where m=min(c,(\textrm{the number of } x\textup{'s contained in } S)) 

  • 3 : Print (\textup{maximum value of }S) - (\textup{minimum value of }S). It is guaranteed that SS is not empty when this query is given.

Constraints

  • 1 \leq Q \leq 2\times 10^5
  • 0 \leq x \leq 10^9
  • 1 \leq c \leq Q
  • When a query of type 3 is given, S is not empty.
  • All values in input are integers.

Output

Print the answers for the queries of type 3 in the given order, separated by newlines.

Sample Input 1

8
1 3
1 2
3
1 2
1 7
3
2 2 3
3

 Sample Output 1

1
5
4

 Tips 1

 Sample Input 2

4
1 10000
1 1000
2 100 3
1 10

 分析及代码实现

 题目大意是对一个初始为空的容器S进行操作,输入指令,包括1:添加一个元素x(输入),2:删除指定元素x m次(删除m个x元素,这里m为c(输入)和x在容器中的个数的最小值,3:输出最大值和最小值之差。

话不多说,直接按题意开码!由于python列表有count和remove方法,暴力法如下:

python brute-force

n=eval(input())
ls=[]
for i in range(n):
    od=list(input().split())
    if od[0]=='1':
        ls.append(eval(od[1]))
    elif od[0]=='2':
        x=eval(od[1])
        t=ls.count(x)
        c=min(eval(od[2]),t)
        for j in range(c):
            ls.remove(x)
    elif od[0]=='3':
        print(max(ls)-min(ls))

理所当然,TLE了。看来用顺序存储结构来做效率太低了,如果删除指定元素需要O(n)的时间复杂度。并且像上面一样进行2:删除元素查询x在列表中出现的元素从而算出m又需要进行线性查找。

分析后发现并不需要按照题目的定义算出这个m,如果输入的c大于Sx的个数,只需将xS中全部删去即可。

为了实现更快的添加和删除元素,考虑使用哈希表进行操作。哈希表中对元素操作的时间复杂度为O(1)。一下就比stl中的vector和python中的list快很多了吧。可以用python中的字典或c++ stl中的unordered_map实现。在使用unordered_map时,需要引入头文件:unordered_map,并不是用#include <map>就可以了。

由于查询3要求出最大最小值,而unordered_map是无序的并且没有max_element和min_element方法,每次求最值又需要进行线性查找,必须得进行优化。

下面使用python实现哈希表的做法:

创建一个字典,key为需要添加到容器中的元素x,value为元素x出现的次数

考虑存储当前最值,每添加一个新的元素,就更新最大最小值,进行删除操作时还有剩余元素时可以不考虑最值变化,只需更新元素剩余数量,那当键值对被删除后怎么做呢?

这时候我们再用python的max,min函数对字典的键求最值进行更新,能比每次进行查询3:线性查找减少不少时间。还要考虑容器被删空的情况,这时要初始化maxs和mins的值。

python-dictionary

s={}
maxs,mins=0,int(1e9+1)
for _ in range(int(input())):
    od=list(map(int,input().split()))
    if len(od)==1: #使用转化为列表后长度判断查询类型,对应查询3
        # print(max(s.keys())-min(s.keys())) 直接用max和min进行线性查找会TLE
        print(maxs-mins)
    elif len(od)==2: #对应查询1
        if od[1] in s:
            s[od[1]]+=1 #值加一
        else:
            s[od[1]]=1 #创建键值对
            #这样写还可能会超时,可以不用系统函数
            # 条件判断od[1]>max od[1]<min再更新
            #min,max函数中上述条件取等号会增加指令执行次数
            maxs,mins=max(maxs,od[1]),min(mins,od[1]) #更新最值
    elif len(od)==3: #对应查询2
        if od[1] in s:
            s[od[1]]-=od[2]
            if s[od[1]]<=0: #删除键值对
                s.pop(od[1])
                if len(s)==0:
                    maxs,mins = 0,int(1e9+1)
                else: #优化后只需在这里线性查找
                    maxs, mins = max(s.keys()), min(s.keys())

这个优化需要用两个变量存储实时最值,因为哈希表元素是无序的,如果用有序的容器来存储,虽然效率比不上哈希表,但是求最值的过程比上面要快且简便。

map是STL中的一个关联容器,和python中的数据类型字典一样,map 类型变量中的元素也是由键-值对组成,没有重复的键,其底层实现是红黑树,最重要的是map中的元素实时都是有序的。map中的key和value是一个pair结构中的两个分量,可以使用下标的方式插入数据。

value默认是0,所以可以这样写:mp[x]+=1

使用erase方法可删除数据,erase(iterator)删除迭代器指向的元素,erase(key)删除键值对

由于是有序的,最大值就是迭代器rbegin指向的元素,最小值就是begin指向的元素

stl-map

#include<iostream>
#include<map>
using namespace std;
int main()
{
    int q;
    cin>>q;
    map<int,int> mp;
    while (q--)
    {
        int a;
        cin>>a;
        if (a==1)
        {
            int x;
            cin>>x;
            mp[x]+=1;
        }
        else if (a==2)
        {
            int x,c;
            cin>>x>>c;
            mp[x]-=c;
            if (mp[x]<=0)
                mp.erase(x);
        }
        else if (a==3)
        {
            cout<<(*mp.rbegin()).first-(*mp.begin()).first<<endl;//注意运算符的优先级
        }
    }
    return 0;
}

还可以用multiset来实现。(嘿嘿,原题在描述这个容器时用的就是multiset),方法和map差不多

引入头文件:#include <set>

set中一个元素只能出现一次,而multiset可以存放多个相同元素,和map一样是有序的

使用insert插入元素 ,删除元素erase方法和map相同,但map方法我们存放的是键值对(值x对应的pair元素只有一个,用value来判断这个值出现的次数或者说是否存在),multiset是单个题目中要求的元素,所以用multiset更加直观,删除时也要删除多次。

stl-multiset

#include<iostream>
#include<set>
using namespace std;
int main()
{
    int q;
    cin>>q;
    multiset<int> ms;
    while (q--)
    {
        int a;
        cin>>a;
        if (a==1)
        {
            int x;
            cin>>x;
            ms.insert(x);
        }
        else if (a==2)
        {
            int x,c;
            cin>>x>>c;
            multiset<int>::iterator it=ms.find(x);
            while (c--&&it!=ms.end()) //如果所有的x都已经删除it就指向end
            {
                ms.erase(it);
                it=ms.find(x);
            }
        }
        else if (a==3)
        {
            cout<<(*ms.rbegin()-*ms.begin())<<endl;
        }
    }
    return 0;
}

 

 变式题目(multiset/priority_queue)

其实我在比赛中遇到这个问题时并不会stl中的map和multiset QAQ,但我想到了之前遇到的另外一道题,这题当时我也不会做,后面得知可用优先队列。AtCoder这道题就像是下面这道题的升级版

 回头过来看,这题也可以用multiset来做,或者说priority_queue能做的multiset都能做

#include<iostream>
#include<set>
using namespace std;
int main()
{
    int q;
    cin>>q;
    multiset <int> s;
    while (q--)
    {
        char od;
        cin>>od;
        if (od=='i')
        {
            int t;
            cin>>t;
            s.insert(t);
        }
        else if (od=='q')
        {
            cout<<*s.begin()<<endl;
            s.erase(s.begin());
        }
    }
    return 0;
}

原来的优先队列解法 

#include<iostream>
#include<queue>
using namespace std;
int main()
{
    int n;
    cin>>n;
    priority_queue <int,vector<int>,greater<int> > q;
    while (n--)
    {
        char od;
        cin>>od;
        if (od=='i')
        {
            int t;
            cin>>t;
            q.push(t);
        }
        else if (od=='q')
        {
            cout<<q.top()<<endl;
            q.pop();
        }
    }
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值