Set / Multiset 集合容器 题库日记

set 集合容器

1. 概要

​ 在头文件 < s e t > \color{Purple}< set > <set> 中声明,使用类似树的结构(基于红黑树的平衡二叉树》) 存储数据并自动将数据 (无重复值)从小到大排列

​ 构建目的: 为了快速检索 (O(logn))

访 问 元 素 \color{Orange}访问元素 访:通过迭代器进行,类似于指针,可以通过它指向容器中某个元素的地址

set<int> s;

//插入元素,不会重复插入
s.insert(x); 

/*删除操作*/
s.erase(s.begin());
s.erase(x);

/*正向迭代器,正向遍历元素*/
set<int>::iterator p; // auto p = s.begin()
for(p = s.begin(); p != s.end(); p ++ )
    cout << *p << " ";

/*查找元素,并返回指向该元素的迭代器,
若不存在,则返回值为 s.end()*/
p = s,find(x);
if(p != s.end())
    cout<< *p;

/*set中元素个数,时间复杂度 O(1)*/
cout<< s.size();
/*set中值为 x 的元素的个数,时间复杂度度 O(logn)*/
s.count(x);

/*清空所有元素*/
s.clear();

使用 insert()将元素插入集合set中时,容器默认为从小到大的顺序插入元素,当我们需要其他顺序时,需要自行编写比较函数

非 结 构 体 \color{Turquoise}非结构体

struct cmp
{
   bool operator() (const int &a, const int &b) const {
       return  a < b; // 从小到大
       // return a > b  // 从大到小
   }
};
set<int, cmp> s;// 其实也可以这样写(从大到小) :set<int, greater<int> > s; 

重载运算符可以使得自定义类型像基本数据类型一样支持加,减,乘,除,自加等各种操作。operator 和 运算符一起使用,表示运算符重载函数,我们可以将 operator 和 运算符 (例如 operator())视为函数名

参数定义 为 const int & a,和 const’ int & b 这种形式:

  1. 防止a和b被修改
  2. 通过引用变量的方式减少系统开销

末尾的 const 也是为了防止修改

结 构 体 \color{Turquoise}结构体

​ 当元素是结构体时,必须重载运算符 "   <   " \color{Red}"~<~" " < "

struct Node{
    string name;
    double score;
    bool operator<(const Node &a)const{
        return a.score < score; // 从大到小
        // 若改为  " > " 则从小到大
    }
}node;

set<Node> s;

2. 题目

2.1 问卷调查

【题目描述】
小光用计算机生成了N个1~1 000的随机整数,对于其中重复的数,只保留一个,把其余相同的数去掉,然后把这些数从小到大排序,按照排好的顺序查找对应的学员做调查。请协助小光完成“去重”与“排序”的工作
【输入格式】
输入有两行,第一行为一个正整数,表示所生成的随机数的个数 N( N<=100)。第二行有N个以空格隔开的正整数,表示所生成的随机数。
【输出格式】
输出有两行,第一行为一个正整数M,表示不相同的随机数的个数.
第二行为M个以空格隔开的正整数,表示从小到大排好序的不相同的随机数

【输入样例】

10
20 40 32 67 40 20 89 300 400 15

【输出样例】

8
15 20 32 40 67 89 300 400

思路

​ 调用set存储元素x,然后输出即可

Code

void solve()
{	
    int n;
    cin>>n;
    
    set<int> s;
    
    for(int i = 0; i < n; i ++ )
    {
        int x;
        cin >>x;
        
        s.insert(x);
        
    }
    for(auto x : s)
       cout<< x << " ";
}

2.2 两倍

【题目描述】
有一组随机产生的包含2~15个不重复的正整数的列表,要求说出这个列表中有多少对数字,其中一个数字是该列表中的其他数字的两倍。例如列表为1 4 3 2 9 7 18 22,由于2是1的两倍、4是2的两倍、18是9的两倍,因此答案为3
【输入格式】
每组测试数据为一行,每行包含2~15个不重复的正整数(均不大于99),每行末尾的0仅作为结束标志使用,一行上只有一个整数-1表示文件的结束
【输出格式】
每组测试数据应当输出一行,打印出测试案例中具有两倍关系的数的个数

【输入样例】

1 4 3 2 9 7 18 22 0
2 4 8 10 0
7 5 11 13 1 3 0
-1

【输出样例】

3
2
0

思路

​ 数据输入set集合容器中,会自动从小到大排序。在调用 count 读出每个元素的两倍的元素是否大于等于1即可

Code

typedef long long LL;
void solve()
{
    int  x;
    scanf("%d", &x);
   	
    set<int> s;
    while(x != -1)
    {
        s.insert(x);
        while(~scanf("%d", &x) && x != 0)
            s.insert(x);
        LL ans = 0;
        for(auto x : s)
            if(s.count(x<<1)) ans++;
        
        cout<< ans << " ";
        s.clear();
        scanf("%d", &x);
    }
}

multiset多重集合容器

1.概要

​ multiset多重集合容器在头文件 < s e t > \color{Purple}<set> <set>中定义,它可以看成序列,序列中可以存在重复的数。multiset能时刻保证序列中的数是有序的(默认从小到大排序)

​ 插入一个数或删除一个数: 都能够在O(logn)的时间内完成

multiset<int> s;
//插入元素,可以重复插入
s.insert(x); 

/*删除操作*/
s.erase(s.begin());
s.erase(x);

/*正向迭代器,正向遍历元素*/
set<int>::iterator p; // auto p = s.begin()
for(p = s.begin(); p != s.end(); p ++ )
    cout << *p << " ";

/*查找元素,并返回指向该元素的迭代器,
若不存在,则返回值为 s.end()*/
p = s,find(x);
if(p != s.end())
    cout<< *p;

/*multiset中元素个数,时间复杂度 O(1)*/
cout<< s.size();
/*multiset中值为 x 的元素的个数,时间复杂度度 O(logn)*/
s.count(x);

/*清空所有元素*/
s.clear();

/*lower_bound() / upper_bound()*/
int y ;
y  = *s.lower_bound(x); // 第一个大于等于x的元素为 y
    y = *s.upper_bound(x); // 第一个大于x的元素为 y

2. 题目

3.1 12 ! 配对

【题目描述】
找出输入数据中有多少对数两两相乘的积为12!
【输入格式】
输入数据中第一行为个数n,随后是n个整数m( 1 <= m <= 2^32
【输出格式】
输出有多少对数两两相乘的积为12!

【输入样例】

8
1 10000 159667200 9696 38373635 1000000 479001600 3

【输出样例】

2

思路

​ 对于每个元素判断是否是 12! 的因子,若是则从multiset中查找元素对

  • 若找到对应元素,使ans + 1,并将此元素从多重集合容器中删除
  • 若找不到则将此因子插入multiset中,以供后续输入元素查找对应因子

Code

typedef long long LL;
typedef unsigned int UI;

void solve()
{
    int n;
    cin>> n;
    
    multiset<UI> s;
    int f12 = 479001600;
    LL ans = 0;
    for(int i = 0; i < n; i ++ )
    {
        int x;
        cin>>x;
        if(f12 % x == 0)
        {
            auto p = s.find(f12/x);
            if(p!= s.end())
               ans++, s.erase(*p);
            else
                s.insert(x);
        }
      
     cout << ans << " ";
    }
}

3.2 01排序

【题目描述】
将01串首先按长度由小到大排序,长度相同时按1的个数由少到多排序,1的个数相同时再按ASCII值排序
【输入格式】
输入数据中第一行为整数n,表示有n个01串,随后是n个01串,01串的长度不大于256个字符
【输出格式】
重新排列01串的顺序,使得01串按题目描述的方式排序

【输入样例】

6
10011111
00001101
1010101
1
0
1100

【输出样例】

0
1
1100
1010101
00001101
10011111

思路

​ 手写cmp函数,按照题目模拟即可

Code

#include<algorithm>
//algorithm头文件定义了一个count的函数,其功能类似于find。这个函数使用一对迭代器和一个值做参数,返回这个值出现次数的统计结果

struct cmp
{
    bool operator() (const string &a, const string &b) const 
    {
        if(a.length() != b.length())
            return a.length() < b.length();
        int c1 = count(a.begin(), a.end(), '1');
        int c2 = count(b.begin(), b.end(), '1');
        
        return c1 != c2 ? c1 < c2 : a < b;
    }
};
void solve()
{
    int n;
    cin >> s;
    
    multiset<string, cmp> s;
    string str;
    for(int i = 0; i < n; i ++ )
        cin >> str, s.insert(str);
    
    for(auto x : s)
        cout << x << " ";
    
}

3.3 卡片游戏

【题目描述】
Alice和Bob分别有不同的矩形卡片,已知A卡片可以覆盖B卡片的条件是A卡片的高度不小于B卡片的高度且A卡片的宽度不小于B卡片的宽度,每张卡片只能使用一次,而且卡片不能旋转。试计算Alice的卡片可以覆盖Bob的卡片的最大张数(题目来源:HDU 4268)
【输入格式】
输入的第一行是 t(t <= 40 ),表示测试用例的数量。对于每一种情况,第一行是n,表示Alice和Bob分别拥有的卡片数。下面n( n <= 100000)行中的每一行都包含两个整数h(100000000)和w( 1000000000),分别表示Alice的卡片的高度和宽度,再下面的n行表示Bob的卡片的高度和宽度
【输出格式】
对于每个测试用例,使用一行包含一个数字的方法输出结果

【输入样例】

1
3
2 3
5 7
6 8
4 1
2 5
3 4

【输出样例】

2

思路

​ 贪心 + multiset:输入数据后,按照从大到小的顺序排序。对于 Alice 的每张纸牌 a[ i ], 将Bob中高度h 小于Alice 的纸牌的 宽度w 存入multiset中,然后从multiset中找出 Bob 中宽度大于当前Alice宽度值的第一个值的位置(upper_bound),再前移一个位置,即为 Alice 能够覆盖Bob的纸牌。从multiset中删除被覆盖的纸牌,并使ans + 1

Code

const int N = 100010;
typedef long long LL;

struct Node{
    LL h, w;
    bool operator< (const Node &t) const
    {
        if(t.h != h)
            return t.h > h;

        return t.w > w;
    }
}a[N], b[N];
void solve()
{
    int t, n;
    scanf("%d", &t);
    
    multiset<LL> s;
    while(t--)
    {
        scanf("%d", &n);
        
        for(int i = 0; i < n; i ++ )
           scanf("%lld%lld", &a[i].h, &a[i].w);
        for(int i = 0; i < n; i ++ )
            scanf("%lld%lld", &b[i].h, &b[i].w);
        
        LL ans = 0;
        sort(a, a + n);
        sort(b, b + n);
        s.clear();
        for(int i = 0, j = 0; i < n; i ++ )
        {
            while(j < n && a[i].h >= b[j].h)
                s.insert(b[j++].w);

            if(s.empty()) continue;
            auto p = s.upper_bound(a[i].w);
            if(p != s.begin())
            {
                p--;
                s.erase(p);
                ans++;
            }
        }
        
     cout << ans << "\n";
        
    }
}

参考资料

【1】 《编程竞赛宝典:C++语言和算法入门》张新华著,人民邮电出版社,ISBN:978-7-115-55461-1

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值