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 这种形式:
- 防止a和b被修改
- 通过引用变量的方式减少系统开销
末尾的 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