STL之set——从“Luogu P2234 [HNOI2002]营业额统计”切入

Luogu题单——线性表  聚焦了多种线性表数据结构的运用,特此进行上手总结。

部分参考c语言中文网 在此鸣谢!


前言

集合是C++STL容器中的重要一员,有着元素唯一等特性,本篇博客将从Luogu相关题目入手,整理相关的知识,归纳总结。


一、题目

题目描述

Tiger 最近被公司升任为营业部经理,他上任后接受公司交给的第一项任务便是统计并分析公司成立以来的营业情况。

Tiger 拿出了公司的账本,账本上记录了公司成立以来每天的营业额。分析营业情况是一项相当复杂的工作。由于节假日,大减价或者是其他情况的时候,营业额会出现一定的波动,当然一定的波动是能够接受的,但是在某些时候营业额突变得很高或是很低,这就证明公司此时的经营状况出现了问题。经济管理学上定义了一种最小波动值来衡量这种情况:当最小波动值越大时,就说明营业情况越不稳定。

而分析整个公司的从成立到现在营业情况是否稳定,只需要把每一天的最小波动值加起来就可以了。你的任务就是编写一个程序帮助 Tiger 来计算这一个值。

我们定义,一天的最小波动值 =​。

特别地,第一天的最小波动值为第一天的营业额。

输入格式

第一行为正整数 n​ ,表示该公司从成立一直到现在的天数,接下来的 nn 行每行有一个整数 a​,表示第 i 天公司的营业额,可能存在负数。

输出格式

输出一个正整数,即每一天最小波动值的和,保证结果小于 ​。

输入输出样例

​ 

说明/提示

二、算法分析

1.题义分析

简单来说,就是给定一个数,寻求当前序列中与该数相差最小的数,然后将该数插入序列中。 

2.算法解决

Ⅰ各类方法

①需要比较大小,那么排个序是否就ok了呢?看看巨犇的题解,emmmm在注意复杂度的情况下,确实可以。

​ ——来自Luogu lzl123 

(但是O(n^2)的复杂度,需要开O2才能过,不过似乎最新表明有几个点已经无法通过了)

②平衡树(什么splay 啊~,,,QAQ 对不起,本蒟蒻在努力学)

③emmmm说好了学数据结构,那么什么样的数据结构能实现呢? 

Ⅱ 数据结构方法

①链表

②STL大法之本篇主角之集合,set

三、STL之set

都知道集合内元素不能重复吧~

1.set简介

set 容器定义于<set>头文件,并位于 std 命名空间中。因此如果想在程序中使用 set 容器,该程序代码应先包含如下语句:

#include <set>
using namespace std;

和 map、multimap 容器不同,使用 set 容器存储的各个键值对,要求键 key 和值 value 必须相等
举个例子,如下有 2 组键值对数据:

{<'a', 1>, <'b', 2>, <'c', 3>}
{<'a', 'a'>, <'b', 'b'>, <'c', 'c'>}

显然,第一组数据中各键值对的键和值不相等,而第二组中各键值对的键和值对应相等。对于 set 容器来说,只能存储第 2 组键值对,而无法存储第一组键值对。

基于 set 容器的这种特性,当使用 set 容器存储键值对时,只需要为其提供各键值对中的 value 值(也就是 key 的值)即可。仍以存储上面第 2 组键值对为例,只需要为 set 容器提供 {'a','b','c'} ,该容器即可成功将它们存储起来。
 

map、multimap 容器都会自行根据键的大小对存储的键值对进行排序,set 容器也会如此,只不过 set 容器中各键值对的键 key 和值 value 是相等的,根据 key 排序,也就等价为根据 value 排序。

另外,使用 set 容器存储的各个元素的值必须各不相同。更重要的是,从语法上讲 set 容器并没有强制对存储元素的类型做 const 修饰,即 set 容器中存储的元素的值是可以修改的。但是,C++ 标准为了防止用户修改容器中元素的值,对所有可能会实现此操作的行为做了限制,使得在正常情况下,用户是无法做到修改 set 容器中元素的值的

切勿尝试直接修改 set 容器中已存储元素的值,这很有可能破坏 set 容器中元素的有序性,最正确的修改 set 容器中元素值的做法是:先删除该元素,然后再添加一个修改后的元素。

2.创建C++ set容器的几种方法 

1) 调用默认构造函数,创建空的 set 容器。比如:

//std::set<std::string> myset;
set<string> myset;

2) 除此之外,set 类模板还支持在创建 set 容器的同时,对其进行初始化。例如:

std::set<std::string> myset{"http://c.biancheng.net/java/",
                            "http://c.biancheng.net/stl/",
                            "http://c.biancheng.net/python/"};

由此即创建好了包含 3 个 string 元素的 myset 容器。由于其采用默认的 std::less<T> 规则,因此其内部存储 string 元素的顺序如下所示:

"http://c.biancheng.net/java/"
"http://c.biancheng.net/python/"
"http://c.biancheng.net/stl/"

 3) set 类模板中还提供了拷贝(复制)构造函数,可以实现在创建新 set 容器的同时,将已有 set 容器中存储的所有元素全部复制到新 set 容器中。

例如,在第 2 种方式创建的 myset 容器的基础上,执行如下代码:

std::set<std::string> copyset(myset);
//等同于
//std::set<std::string> copyset = myset

该行代码在创建 copyset 容器的基础上,还会将 myset 容器中存储的所有元素,全部复制给 copyset 容器一份。

4) 在第 3 种方式的基础上,set 类模板还支持取已有 set 容器中的部分元素,来初始化新 set 容器。例如:

std::set<std::string> myset{ "http://c.biancheng.net/java/",
                    "http://c.biancheng.net/stl/",
                    "http://c.biancheng.net/python/" };
std::set<std::string> copyset(++myset.begin(), myset.end());

由此初始化的 copyset 容器,其内部仅存有如下 2 个 string 字符串:

"http://c.biancheng.net/python/"
"http://c.biancheng.net/stl/"

 5) 以上几种方式创建的 set 容器,都采用了默认的std::less<T>规则。其实,借助 set 类模板定义中第 2 个参数,我们完全可以手动修改 set 容器中的排序规则。比如:

std::set<std::string,std::greater<string> > myset{
    "http://c.biancheng.net/java/",
    "http://c.biancheng.net/stl/",
    "http://c.biancheng.net/python/"};

通过选用 std::greater<string> 降序规则,myset 容器中元素的存储顺序为:

"http://c.biancheng.net/stl/"
"http://c.biancheng.net/python/"
"http://c.biancheng.net/java/"

3. C++ STL set容器包含的成员方法

表 1 C++ set 容器常用成员方法
成员方法功能
begin()返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
end()返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
rbegin()返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
rend()返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。
cbegin()和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
cend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crbegin()和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
crend()和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。
find(val)在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
lower_bound(val)返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
upper_bound(val)返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。
equal_range(val)该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。
empty()若容器为空,则返回 true;否则 false。
size()返回当前 set 容器中存有元素的个数。
max_size()返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。
insert()向 set 容器中插入元素。
erase()删除 set 容器中存储的元素。
swap()交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。
clear()清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。
emplace()在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。
emplace_hint()在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。
count(val)在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。

 下面程序演示了表 1 中部分成员函数的用法:

#include <iostream>
#include <set>
#include <string>
using namespace std;
int main()
{
    //创建空set容器
    std::set<std::string> myset;
    //空set容器不存储任何元素
    cout << "1、myset size = " << myset.size() << endl;
    //向myset容器中插入新元素
    myset.insert("http://c.biancheng.net/java/");
    myset.insert("http://c.biancheng.net/stl/");
    myset.insert("http://c.biancheng.net/python/");
    cout << "2、myset size = " << myset.size() << endl;
    //利用双向迭代器,遍历myset
    for (auto iter = myset.begin(); iter != myset.end(); ++iter) {
        cout << *iter << endl;
    }
    return 0;
}

程序执行结果为:

1、myset size = 0
2、myset size = 3
http://c.biancheng.net/java/
http://c.biancheng.net/python/
http://c.biancheng.net/stl/

c语言中文网是一个干货满满的宝藏网站,适合你我蒟蒻的初期学习,再次鸣谢!


四、言归正传 

那么set对本题有什么意义呢?

set的各种特性秒杀本题(降维打击,模拟题~)

①元素不重复        本题寻找一个数,距离给定数最近,我们不关心有多少个,而在乎这个数是什么(即有没有,显然肯定是有滴)。那么,set的这种特性就限制不了我们啦。

②默认排序            本题比较大小,本就暗含着排序的要求,一拍即合!

③方法的使用        “寻找”?

再--迭代器寻找比给定数小的数,看看哪个更近。简直 一步解决!!

源代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iomanip>
#include<limits.h>
#include<stack>
#include<cstdlib>
#include<queue>
#include<map>
#include<vector>
#include<set> 

#define INF 0x3f3f3f3f
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)

using namespace std;

set<int> s;
set<int>::iterator c,d;
int n;
int ans=0;

int main()
{
	cin>>n;
	int a;
	s.insert(INF);
	s.insert(-INF);
	//防止内存调用出错 
	int b;
	while(n--)
	{
		cin>>a;
		if(s.size()==2)
		{
			s.insert(a);
			ans+=a;
		}
		else
		{
			c=s.lower_bound(a);
			if(*c!=a)
			{
				
				d=c;
				d--;
				//d=s.upper_bound(a);
				ans+=min(abs(a-*c),abs(a-*d));
				s.insert(a);			
			} 
		}
	}
	cout<<ans;
 	return 0;
}

总结

在学习数据结构的路上。与诸君共勉!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值