知识点:
DAY1:
1.双端队列deque
双开口,时间复杂度O(1),可以有两个栈实现,是队列和栈的底层逻辑
deque的底层不是真正连续的空间,而是由一段段连续的小空间拼接而成的(节点node指向缓冲区buffer,迭代器iterator的first指向缓冲区首地址,last指向缓冲区的末地址,node指向中控区节点,cur为缓冲区中的内容),类似于一个动态的二维数组。为了实现deque的迭代器,deque采用了一个chunk大小的策略,即将容器等分成多个chunk(块),每个chunk内部是连续的一段空间,但是chunk之间并不连续。deque的迭代器会维护一个指向当前chunk的指针,以及当前指针所在的块内的元素迭代器。当进行迭代器的移动操作时,如果需要跨过chunk的边界,则会重新指向下一个或上一个chunk的起始位置,并更新当前指针所在的块内的元素迭代器。虽然deque内部的存储并不是连续的,但是通过迭代器的维护,可以实现高效的遍历、插入和删除操作,同时维护其假想连续的结构。具体实现如下图。
缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低
作为stack和queue的底层默认容器的原因:
stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。
在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。
2.队列queue
先进先出,
常用函数:
empty() //检测队列是否为空
size() //返回队列中有效元素的个数
front() //返回队头元素的引用
back() //返回队尾元素的引用
push_back()//在队列尾部入队列 同push()
pop_front()//在队列头部出队列 同pop()
3.栈stack
一端进一端出,先进后出,以deque为底层逻辑。
常用函数:
empty()//检测stack是否为空
size()//返回stack中元素的个数
top() //返回栈顶元素的引用
push()//将元素val压入stack中
pop() //将stack中尾部的元素弹出
4.优先队列
根据严格的弱排序标准。
堆:默认为满二叉树,构建最大二叉树。先插入再判断需不需要上浮,每个节点和父节点比较,若大于,互相交换
举例:3 1 6 5 2 4
插入3:堆:3
插入1:堆:3 1
插入6:堆:6 1 3
插入5:堆:6 5 3 1
插入2:堆:6 5 3 1 2
插入4:堆:6 5 4 1 2 3
底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素
标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用。注意:默认情况下priority_queue是大堆。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆
常用函数:
empty() //检测优先级队列是否为空,是返回true,否则返回false
top() //返回优先级队列中最大(最小元素),即堆顶元素
push(x) //在优先级队列中插入元素x
pop() //删除优先级队列中最大(最小)元素,即堆顶元素
函数实现:
#include <iostream>
using namespace std;
#include <vector>
#include <queue>
#include <functional> // greater算法的头文件
int main(){
// 默认情况下,创建的是大堆,其底层按照小于号比较
vector<int> v{3,2,7,6,0,4,1,9,8,5};
priority_queue<int> q1;
for (auto& e : v)
q1.push(e);
cout << q1.top() << endl;
// 如果要创建小堆,将第三个模板参数换成greater比较方式
priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
cout << q2.top() << endl;
return 0;
}
附:greater算法代码:
template <typename Ty>
struct greater {
__device__ __forceinline__ Ty operator()(Ty arg1, Ty arg2) const {
return (arg1 < arg2) ? arg2 : arg1;
}
};
对结构体实现的优先队列:
#include<bits/stdc++.h>
using namespace std;
struct node{
string name;
int score;
bool operater<(const node &a)const{//重载<,将node类型的大小转换为node.score的大小
return a.score>score; //注意是>
}
}
int main(){
struct node n1;
priority_queue<node,vector<int> > p;
}
DAY2:
1.Set(集合):
类似于数学中的集合,没有相同的元素,默认从小到大排列,可以用来排序和去重。set在底层是用平衡搜索树(红黑树)实现的,所以在set当中查找某个元素的时间复杂度为(log N)
定义方法:
1.构造某类型的空容器:
set<int> s1; //构造int类型的空容器
2.拷贝构造某类型set容器的复制品。
set<int> s2(s1); //拷贝构造int类型s1容器的复制品
3.使用迭代器拷贝构造某一段内容。
string str("abcdef");
set<char> s3(str.begin(), str.end()); //构造string对象某段区间的复制品
4.构造一个某类型的空容器,比较方式指定为大于。
set < int, greater<int>> s4; //构造int类型的空容器,比较方式指定为大于
set常用函数:
注:由于set中每个元素最多只能有一个,所以st.count(x)为0(无元素)或1(有元素)。当erase函数中的值在set中不存在时会报错。
相关迭代器函数:
迭代器操作:
测试实例:
void test_set2()
{
std::set<int> myset;
std::set<int>::iterator itlow, itup;
for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
//itlow = myset.lower_bound(30); //
itlow = myset.lower_bound(25); // >= val值位置的iterator
itup = myset.upper_bound(70); // > val值位置的iterator
// [25, 70]
//删除左闭右开
myset.erase(itlow, itup); // 10 20 70 80 90
for (auto e : myset)
{
cout << e << " ";
}
cout << endl;
}
结果:lower_bound返回的是>=的val(30).
upper_bound返回的是>的 val(80);
2.multiset
与set相比,元素可重复,默认从小到大排序,也是基于红黑树原理,时间复杂度为O(log N)。与set类似。
注:当在multiset中使用erase函数时会删除所有的那个值。(可使用迭代器解决)
3.map
通过键值来查找/排序(默认小于),键值不能改变,但是值可以改变,键值和值的内容可以不同。访问单个元素速度比unordered_map容器慢,但允许对元素进行直接迭代。支持下标访问符,即m[1]返回1对应的值底层是用平衡搜索树(红黑树)实现的,在map当中查找某个元素的时间复杂度为logN。构造同上。
在map中插入键值对
法1:使用变量名.insert(pair<键值类型名,值类型名>(键值,值))来插入
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int main() {
int n, m;
map<int, int> m1;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> m;
m1.insert(pair<int, int>(m, 1));
}
return 0;
}
法2:调用make_pair模版插入
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int main() {
int n, m;
map<int, int> m1;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> m;
m1.insert(make_pair(m, 1));
}
return 0;
}
insert类型的返回值也是pair对象
1.若待插入元素的键值key在map当中不存在,则insert函数插入成功,并返回插入后元素的迭代器和true。
2.若待插入元素的键值key在map当中已经存在,则insert函数插入失败,并返回map当中键值为key的元素的迭代器和false。
map的查找
函数原型如下:
iterator find (const key_type& k);
map的查找函数是根据所给key值在map当中进行查找,若找到了,则返回对应元素的迭代器,若未找到,则返回容器中最后一个元素下一个位置的正向迭代器(即end)。
map<int, string> m;
m.insert(make_pair(2, "two"));
m.insert(make_pair(1, "one"));
m.insert(make_pair(3, "three"));
//获取key值为2的元素的迭代器
map<int, string>::iterator pos = m.find(2);
if (pos != m.end())
{
cout << pos->second << endl; //two
}
删除函数
原型:
//删除函数1
size_type erase (const key_type& k);
//删除函数2
void erase(iterator position);
我们既可以根据key值删除指定元素,也可以根据迭代器删除指定元素,若是根据key值进行删除,则返回实际删除的元素个数。
map<int, string> m;
m.insert(make_pair(2, "two"));
m.insert(make_pair(1, "one"));
m.insert(make_pair(3, "three"));
//方式一:根据key值进行删除
m.erase(3);
//方式二:根据迭代器进行删除
map<int, string>::iterator pos = m.find(2);
if (pos != m.end())
{
m.erase(pos);
}
map的[ ]运算符重载
函数原型
mapped_type& operator[] (const key_type& k);
返回值
(*((this->insert(make_pair(k, mapped_type()))).first)).second
可分为以下三个步骤
1.调用insert函数插入键值对。
2.拿出从insert函数获取到的迭代器。
3.返回该迭代器位置元素的值value。
分解代码如下
mapped_type& operator[] (const key_type& k)
{
//1、调用insert函数插入键值对
pair<iterator, bool> ret = insert(make_pair(k, mapped_type()));
//2、拿出从insert函数获取到的迭代器
iterator it = ret.first;
//3、返回该迭代器位置元素的值value
return it->second;
}
总结:
1.如果k不在map中,则先插入键值对<k, V()>,然后返回该键值对中V对象的引用。
2.如果k已经在map中,则返回键值为k的元素对应的V对象的引用。
遍历
1.用正向迭代器遍历:
map<int, string> m;
m.insert(make_pair(2, "two"));
m.insert(make_pair(1, "one"));
m.insert(make_pair(3, "three"));
//用正向迭代器进行遍历
map<int, string>::iterator it = m.begin();
while (it != m.end())
{
cout << "<" << it->first << "," << it->second << ">" << " ";
it++;
}
cout << endl; //<1,one> <2,two> <3,three>
2.for循环遍历:
map<int, string> m;
m.insert(make_pair(2, "two"));
m.insert(make_pair(1, "one"));
m.insert(make_pair(3, "three"));
//用范围for进行遍历
for (auto e : m)
{
cout << "<" << e.first << "," << e.second << ">" << " ";
}
cout << endl; //<1,one> <2,two> <3,three>
其他成员函数:
multimap
底层逻辑与map一样(红黑树),区别:multimap允许键值冗余,即multimap容器当中存储的元素是可以重复的。
find函数与count函数:
由于multimap容器允许键值冗余,调用[ ]运算符重载函数时,应该返回键值为key的哪一个元素的value的引用存在歧义,因此在multimap容器当中没有实现[ ]运算符重载函数。
DAY3:
1.关闭同步流;
导致无法使用scanf和printf(会使cin和cout效率提高)
ios::sync_with_stdio(0);
cin.tie(0);
2.使用 std::merge合并有序数组:
普通代码:
std://vector<int> results(n+m);
int i=0,j=0,k=0;
whhile(i<n&&j<m){
if(arr1[i]<arr2[j]) result[k++]=arr1[i++];
else result[k++] = arr2[j++];
}
while(i<n) result[k++]=arr1[i++];
while(j<m) result[k++]=arr2[j++];
竞赛代码 :
#include<algorithm>
std::merge(arr1,arr1+n,arr2,arr2+m,result);
3.使用cout时限制保留几位小数(默认六位)
cout << fixed << setprecision(x)//fixed是不用科学计数法,setprecision
是设置小数点后面几位,x即为几位小数。
4.哈希
哈希主要有三个组成部分:
- 键:键可以是任何字符串或整数,它们作为输入输入提供给哈希函数,哈希函数是确定数据结构中项目存储的索引或位置的技术。
- 哈希函数: 哈希函数接收输入键并返回称为哈希表的数组中元素的索引。该索引称为哈希索引。
- 哈希表:哈希表是一种数据结构,它使用称为哈希函数的特殊函数将键映射到值。Hash 以关联方式将数据存储在一个数组中,其中每个数据值都有自己唯一的索引。
碰撞:由于哈希函数为我们获取了一个键的一个小数字,该键是一个大整数或字符串,因此两个键可能会产生相同的值。新插入的键映射到哈希表中已占用的插槽的情况称为冲突,必须使用某种冲突处理技术进行处理。
处理碰撞的方法主要有两种:
独立链接:
在单独的链接中,如果两个不同的元素具有相同的哈希值,那么我们将两个元素一个接一个地存储在同一个链表中。
哈希的性能可以在以下假设下进行评估:每个键被哈希到表的任何插槽的可能性相同(简单的统一哈希)。
m = 哈希表
中的插槽数 n = 要插入哈希表的键数负载系数 α = n/m
预计搜索时间 = O(1 + α)
预期删除时间 = O(1 + α)插入时间 = O(1)如果 α 为 O(1),则搜索插入和删除的时间复杂度为 O(1)
开放式寻址:
开放寻址是一种处理冲突的方法。在开放式寻址中,所有元素都存储在哈希表本身中。因此,在任何时候,表的大小都必须大于或等于键的总数(请注意,如果需要,我们可以通过复制旧数据来增加表的大小)。此方法也称为封闭哈希。整个过程都是基于探测的。
1. 线性探测:
在线性探测中,从哈希的原始位置开始按顺序搜索哈希表。如果我们得到的位置已经被占用,那么我们会检查下一个位置。
用于重新散列的函数如下:rehash(key) = (n+1)%table-size。
2. 二次探测
如果你仔细观察,那么你就会明白,探测之间的间隔将与哈希值成比例地增加。二次探测是一种方法,借助它我们可以解决上面讨论的聚类问题。这种方法也称为中方法。在这种方法中,我们寻找 i2'中的第 i个插槽的迭代。我们总是从原始哈希位置开始。如果只有该位置被占用,那么我们将检查其他插槽。
设 Hash(X) 为使用哈希函数计算的时隙索引。
如果插槽 hash(x) % S 已满,则我们尝试 (hash(x) + 1*1) % S
If (hash(x) + 1*1) % S 也已满,然后我们尝试 (hash(x) + 2*2) % S
如果 (hash(x) + 2*2) % S 也已满,那么我们尝试 (hash(x) + 3*3) % S
3. 双重哈希
探测器之间的间隔由另一个哈希函数计算。双重哈希是一种以优化方式减少聚类的技术。在这种技术中,探测序列的增量是使用另一个哈希函数计算的。我们使用另一个哈希函数 hash2(x) 并在 i 中查找 i*hash2(x) 。
设 Hash(X) 为使用哈希函数计算的时隙索引。
如果 slot hash(x) % S 已满,则我们尝试 (hash(x) + 1*hash2(x)) % S
如果 (hash(x) + 1*hash2(x)) % S 也已满,则我们尝试 (hash(x) + 2*hash2(x)) % S
如果 (hash(x) + 2*hash2(x)) % S 也已满,则我们尝试 (hash(x) + 3*hash2(x)) % S
冲突处理技术的选择可能会对哈希表的性能产生重大影响。线性探测既简单又快速,但它可能会导致聚类(即,密钥存储在长时间连续运行中的情况),并可能降低性能。二次探测的间隔更大,但它也可能导致聚类,并可能导致从不检查某些插槽的情况。双重哈希更为复杂,但它可以使密钥分布更均匀,并且在某些情况下可以提供更好的性能。
S.No. | 独立链接 | 开放式寻址 |
---|---|---|
1. | 链接更易于实现。 | 开放式寻址需要更多的计算。 |
2. | 在链接中,哈希表永远不会填满,我们总是可以向链中添加更多元素。 | 在开放式寻址中,表可能会变满。 |
3. | 链接对哈希函数或负载因子不太敏感。 | 开放式寻址需要格外小心,以避免聚类和负载因子。 |
4. | 当不知道可以插入或删除多少个键以及多久可以插入或删除一次键时,最常使用链接。 | 当密钥的频率和数量已知时,使用开放式寻址。 |
5. | 由于键是使用链表存储的,因此链接的缓存性能不佳。 | 开放式寻址提供了更好的缓存性能,因为所有内容都存储在同一个表中。 |
6. | 空间浪费(链接中哈希表的某些部分从未使用过)。 | 在开放式寻址中,即使输入未映射到插槽,也可以使用插槽。 |
7. | 链接会为链接使用额外的空间。 | Open addressing中没有链接 |
哈希在C++中的实现(unordered_set与unordered_map)
unordered_set:
与set类似,但是无序,底层逻辑为哈希表。
使用迭代器构造(常用):
unordered_set<int> set3(set1.begin(), set1.end());
使用迭代器遍历:
for(unordered_set<int>::iterator it = set1.begin(); it != set1.end(); ++it){
cout << *it << end;
}
函数同set
unordered_map:
unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。
构造,函数,重载运算符同map
注:unordered_multiset与unordered_multimap不再赘述,与上俩类似。
DAY-4
数组:
定义数组的时候,必须有初始长度,否则将报错。在初始化数据的时候,如果没有全部填完,会用0填补剩余数据。数组名可以统计整个数组在内存中的长度,可以获取数组在内存中的首地址
常用函数:
1.清空数组
第一位为数组名,第二位为填充的元素,第三位为大小。
int a[] = {1,2,3,4,5,6};
memset(a,0,sizeof(a));//数组每个元素都变为0
2. 复制数组
同清空
int a[] = {1,2,3,4,5,6};
int b[sizeof(a)/sizeof(a[0])];
memcpy(b,a,sizeof(a));//数组b就和数组a一样了
3.翻转数组:
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
char b[8] = {'a', 'n', 'v', 'd', 'g', 'e', 'p'};
reverse(b, b + 7);
puts(b);
return 0;
}
注意命名char[]时,要比元素大一位,因为末端需要以"\0"结尾
4.填充替代:
已知数组b[7],fill(b,b+7,1)可以把数组b全部都填充为1,replace(b,b+7,1,2)可以把b数组的所有元素替换为2
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int b[7];
fill(b, b + 7, 1);
for (int i = 0; i < 7; i++) {
cout << b[i];
}
return 0;
}
string字符串:
可直接使用关系运算符(==、!=、<、<=、>、>=)来判断字符串大小(根据Ascii表逐位判断)
string转int:
stoi()函数:
#include<iostrem>
#include<algorithm>
using namespace std;
int main(){
string s1="123";
string s2="6;
cout<<stoi(s1)+stoi(s2);//输出129
}
int转string:
to_string函数:
#include<iostrem>
#include<algorithm>
using namespace std;
int main(){
int a=123;
int b=234;
string s=to_string(a)+to_string(b);
cout<<s;//输出123234
}
string常用函数:
1(substr函数):字符串截取函数,用于获取字符串的子串:
//str.substr(begin,length),用于截取str中以begin为下标长度为length的字串
string s=“asd”;
s1=s.substr(0,1);//结果为a
s2=s.substr(1);//结果为sd
2(find函数):查找字符串中该字符出现位置:
using namespace std;
string s=“asd”;
int a=s.find(‘e’);//可通过 s.find("e")==string::npos来判断有没有找到(有即相等)
int b=s.find(sd);//返回1
第二位默认为0,为查找的下表起始位,第三位为查找个数,默认为整个字符串
string.rfind可逆序查找字符串中字符出现的位置(最后出现)
3.(insert):用于添加字符串。
string& insert(size_t pos,const string&str);
// 在位置 pos 处插入字符串 str
string& insert(size_t pos,const char * s);
// 在位置 pos 处插入字符串 s
string& insert(size_t pos,const char * s,size_t n);
// 在位置 pos 处插入字符串 s 的前 n 个字符
string& insert(size_t pos,size_t n,char c);
// 在位置 pos 处插入 n 个字符 c
4.(erase函数):用于作字符串删除操作(同substr)
string& erase (size_t pos = 0, size_t len = npos); // 删除从 pos 处开始的 n 个字符
iterator erase (const_iterator p); // 删除 p 处的一个字符,并返回删除后迭代器的位置
iterator erase (const_iterator first, const_iterator last); // 删除从 first 到 last 之间的字符,并返回删除后迭代器的位置
string str=“abc”;
str.erase(1,1);//结果为ac
5.(replace函数):用来对字符串的字串作替换处理
//str.replace(begin,length,string s);用于把下标为begin,长度为length的字串替换为s。
string str=“abc”;
str=str.replace(1,2,“lk”);//str=“alk”。
//str.replace(s.begin(),s.begin()+3,“aaa”);给出两个起始位置和终点,把其中的字符串替换为"aaa"
// str.replace(1, 3, “123456”, 3, 5);用"123456"子串的下标[3,5]替换str的[1,3]的位置.
//str.replace(1, 3, 5, ‘a’); //用5个’a’代替str的[1,3]的位置.
6.(reverse()函数):翻转字符串
string s=“lklk”;
reverse(str.begin(),str.end());//翻转整个字符串
reverse(str.begin(),str.begin()+n);//也可以翻转字符串的前n个字符;
7.(clear()函数):清空字符串:
8.(string.size() 函数,string.length() 函数):获得 string 对象的长度。
9.(compare()函数):
1)返回 0,表示相等;
2)返回结果小于 0,表示比较字符串中第一个不匹配的字符比源字符串小,或者所有字符都匹配但是比较字符串比源字符串短;
3)返回结果大于 0,表示比较字符串中第一个不匹配的字符比源字符串大,或者所有字符都匹配但是比较字符串比源字符串长。
int compare(const string&str) const;
int compare(size_t pos,size_t len,const string&str)const; // 参数 pos 为比较字符串中第一个字符的位置,len 为比较字符串的长度
int compare(size_t pos,size_t len,const string&str, size_t subpos,size_t sublen)const;
int compare(const char * s)const;
int compare(size_t pos,size_t len,const char * s)const;
int compare(size_t pos,size_t len,const char * s,size_t n)const;
10.(count函数):
第一位为查找首地址,第二位为尾地址,最后一位是查找的字符。
#include<iostrem>
#include<algorithm>
using namespace std;
int main(){
string s1="abcdcdefcgh";
cout<<s1.cout(s1.begin(),s1.end(),"c");//输出3
}
快速幂:
通过将数字十进制转二进制并进行位运算来快速算出高次幂/高次幂取余。
代码实现
long long pow1(long long base, long long exp,int mod) {
int res=1;
for (; exp; exp >>= 1) { //等同于while循环
if (exp & 1) res = (res * base)%mod;//判断exp二进制末位是否为1,为1则乘
base = (base * base)%mod; //底数自乘
}
return res % mod;
}
矩阵运算:
矩阵乘法:
矩阵A: 1 2
3 4
矩阵B:5 6
7 8
矩阵C:1*5+2*7 1*6+2*8
3*5+4*7 3*6+4*8
矩阵实现:
struct Matrix {
int m[2][2];
Matrix() { memset(m, 0, sizeof m); } //填充矩阵,每个元素都为0。
Matrix operator*(const Matrix& o) const { //重定义*操作符为矩阵乘法
Matrix res;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
for (int k = 0; k < 2; k++) //三重遍历完成矩阵乘法
res.m[i][j] = (res.m[i][j] + 1LL * m[i][k] * o.m[k][j]) % MOD;
return res;
}
};
注:1LL会在运算时把后面的临时数据扩容成long long类型,再在赋值给左边时转回int类型。
题解:
DAY1:
C-表达式求值:
题目:
思路:
读取一个数字,然后用while循环读取一个字符(cin.get()),之后在while循环中再判断读取的字符是否为换行符,如果是则退出。不是则读取一个数字。读取的字符是*则先计算出值并取余,使得栈中的数据只需要相加,最后遍历栈并取余得出答案。
代码实现:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int main() {
stack<int> count;
int a;
int b = 0;
char c='+';
cin >> a;
count.push(a);
while (cin.get(c)) {
if (c=='\n') {
break;
}
cin >> b;
if (c == '+') {
count.push(b%10000);
}
else {
int x = (count.top() * b) % 10000;
count.pop();
count.push(x);
}
}
int sum = 0;
while (!count.empty()) {
sum = (sum + count.top())%10000;
count.pop();
}
cout << sum;
return 0;
}
这道题一开始我的思路是将输入全部读取,根据字符串转数字得出数字,将数字存储进栈,然后根据符号位‘+’还是‘*’选择下一步操作,但是进行*运算时发现还需要读取*之后的数据,代码显得异常复杂臃肿,遂选择转换思路,使用cin.gets函数读取单个字符,使得不用字符串转数字。
H-中位数
题目:
思路:
通过建立一个大堆一个 小堆,将元素两两读取,大的放进大堆,小的放进小堆,并使得小堆个数比大堆大一或相等,则小堆的第一个数就是中位
代码实现:
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int seqLength;
cin >> seqLength;
priority_queue<int> lowerHalf;
priority_queue<int, vector<int>, greater<int>> upperHalf;
int firstNum;
cin >> firstNum;
lowerHalf.push(firstNum);
cout << firstNum << '\n';
for (int i = 1; i < (seqLength + 1) / 2; ++i) {
int numX, numY;
cin >> numX >> numY;
if (numX < lowerHalf.top()) {
lowerHalf.push(numX);
} else {
upperHalf.push(numX);
}
if (numY < lowerHalf.top()) {
lowerHalf.push(numY);
} else {
upperHalf.push(numY);
}
if (lowerHalf.size() > upperHalf.size() + 1) {
upperHalf.push(lowerHalf.top());
lowerHalf.pop();
}
if (upperHalf.size() > lowerHalf.size()) {
lowerHalf.push(upperHalf.top());
upperHalf.pop();
}
cout << lowerHalf.top() << '\n';
}
return 0;
}
本题主要是要注意时间超限(好像测试数据用的很大,只用一个会时间超限)
DAY3:
F - Matryoshkas
题目:
思路:
俄罗斯套娃,while处理多组数据,然后读取数量,将值存入到动态数组a(n)中,再存入map类型的freq中,设置组数result为0.将freq的first和second创建别名为val和count1(c++17及以上支持)。通过判断连续来减去数字个数,遍历完一遍边使result加一,最后输出组数result。
代码实现:
#include <bits/stdc++.h>
using namespace std;
int main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; ++i) cin >> a[i];
map<int, int> freq;
for (int x : a) freq[x]++;
int result = 0;
for (auto &[val, count1] : freq) {
while (count1 > 0) {
int m = 0;
while (freq[val + m] > 0) {
freq[val + m]--;
m++;
}
result++;
}
}
cout << result << "\n";
}
return 0;
}
资料来源:
【C++ STL】 趣学stack&queue&priority_queue【对话情景版】-阿里云开发者社区 (aliyun.com)