题单:【蓝桥杯】蓝桥杯省一秘诀(国赛题单看简介) - 题单 - 洛谷 | 计算机科学教育新生态
状态:嗯嗯今天掌握的不错,set和map确实很方便,需要多看多练
P1102 A-B数对
题目链接:P1102 A-B 数对 - 洛谷
题目描述
给出一串正整数数列以及一个正整数 C ,要求计算出所有满足 A −B =C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
输入格式
输入共两行。
第一行,两个正整数 N ,C。
第二行,N 个正整数,作为要求处理的那串数。
输出格式
一行,表示该串正整数中包含的满足 A −B =C 的数对的个数。
输入输出样例
输入:
4 1
1 1 2 3
输出:
3
说明/提示
对于 75% 的数据,1≤N≤2000。
对于 100% 的数据,1≤N ≤2×105,0≤a**i <230,1≤C<230。
2017/4/29 新添数据两组
这道题刚看的想法当然是模拟了,按照题意模拟,统计个数。然后发现样例并不能全部通过,嗯......因为超时了。
//模拟
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,c;
cin>>n>>c;
vector<int> number(n);
for(int i=0;i<n;i++){
cin>>number[i];
}
int count=0;
for(int j=0;j<n;j++){
for(int k=0;k<n;k++){
if(number[j]-number[k]==c){
count++;
}
}
}
cout<<count<<endl;
return 0;
}
这道题可以用unordered_map,这样就可以全部通过了,是的俺之前压根不会用哈希,这次需要学习一番,set,map的介绍在后面!可以把A-B=C变换成A=B+C,这样就只需要枚举一个B。
//优化版
#include<bits/stdc++.h>
using namespace std;
int main() {
int n, c;
cin >> n >> c;
vector<int> number(n); // 预先分配大小
unordered_map<int, int> freq;//map是键值对
for(int i = 0; i < n; i++) {
cin >> number[i];
freq[number[i]]++;//这里就是把输入的值为键,映射到值上
}
long long count = 0;
// 枚举每个数作为B,找A = B + C
for(int i = 0; i < n; i++) {
int a = number[i] + c;
count += freq[a]; // 统计A出现的次数
}
cout << count << endl;
return 0;
}
P1918 保龄球
题目链接:P1918 保龄球 - 洛谷
题目描述
DL 算缘分算得很烦闷,所以常常到体育馆去打保龄球解闷。因为他保龄球已经打了几十年了,所以技术上不成问题,于是他就想玩点新花招。
DL 的视力真的很不错,竟然能够数清楚在他前方十米左右每个位置的瓶子的数量。他突然发现这是一个炫耀自己好视力的借口------他看清远方瓶子的个数后从某个位置发球,这样就能打倒一定数量的瓶子。
-
◯◯◯
-
◯◯◯ ◯
-
◯
-
◯ ◯
如上图,每个 "◯" 代表一个瓶子。如果 DL 想要打倒 3 个瓶子就在 1 位置发球,想要打倒 4 个瓶子就在 2 位置发球。
现在他想要打倒 m 个瓶子。他告诉你每个位置的瓶子数,请你给他一个发球位置。
输入格式
第一行包含一个正整数 n,表示位置数。
第二行包含 n 个正整数 a**i ,表示第 i 个位置的瓶子数,保证各个位置的瓶子数不同。
第三行包含一个正整数 Q,表示 DL 发球的次数。
第四行至文件末尾,每行包含一个正整数 m ,表示 DL 需要打倒 m 个瓶子。
输出格式
共 Q 行。每行包含一个整数,第 i 行的整数表示 DL 第 i 次的发球位置。若无解,则输出 0。
输入输出样例
输入:
5
1 2 4 3 5
2
4
7
输出:
3
0
说明/提示
【数据范围】
对于 50% 的数据,1≤n ,Q ≤1000,1≤a**i ,m≤105。
对于 100% 的数据,1≤n ,Q ≤100000,1≤a**i ,m≤109。
这道题我刚开始照样还是用了模拟,哈哈哈照样没有全部通过,还是因为超时了。嗯......需改进。
//模拟
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,q;
long long m;
cin>>n;
vector<int> number(n);
for(int i=0;i<n;i++){
cin>>number[i];
}
int flag=0;
cin>>q;
for(int j=0;j<q;j++){
cin>>m;
flag=0;
for(int k=0;k<n;k++){
if(number[k]==m){
flag=1;
cout<<k+1<<endl;
break;
}
}
if(flag==0){
cout<<0<<endl;
}
}
return 0;
}
其实这道题照样还是要用unordered_map来解决,会效率高很多,如果不知道unordered_map是什么,请往下看。
//优化版
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, q;
cin >> n;
unordered_map<int, int> pos; // 瓶子数 → 位置(1-based)
for (int i = 0; i < n; i++) {
int val;
cin >> val;
pos[val] = i + 1; //从1开始
}
cin >> q;
while (q--) {
int m;
cin >> m;
if (pos.count(m)) {//如果找得到,这里面有m为键的值,就输出其位置
cout << pos[m] << endl;
} else {
cout << 0 << endl;
}
}
return 0;
}
P5266 【深基17.例6】学籍管理
题目描述
您要设计一个学籍管理系统,最开始学籍数据是空的,然后该系统能够支持下面的操作(不超过 105 条):
-
插入与修改,格式
1 NAME SCORE:在系统中插入姓名为 NAME(由字母和数字组成不超过 20 个字符的字符串,区分大小写),分数为 SCORE(0<SCORE<231) 的学生。如果已经有同名的学生则更新这名学生的成绩为 SCORE。如果成功插入或者修改则输出OK。 -
查询,格式
2 NAME:在系统中查询姓名为 NAME 的学生的成绩。如果没能找到这名学生则输出Not found,否则输出该生成绩。 -
删除,格式
3 NAME:在系统中删除姓名为 NAME 的学生信息。如果没能找到这名学生则输出Not found,否则输出Deleted successfully。 -
汇总,格式
4:输出系统中学生数量。
输入格式
第一行,输入一个正整数 Q (1≤Q≤105),表示操作数量。
接下来 Q 行,每行先输入一个正整数 o**p (o**p∈[1,4]),表示操作种类。接着:
-
如果 o**p=1,则再输入一个字符串 NAME 以及一个正整数 SCORE,含义见题目描述。
-
如果 o**p=2,则再输入一个字符串 NAME,含义见题目描述。
-
如果 o**p=3,则再输入一个字符串 NAME,含义见题目描述。
-
如果 o**p=4,则无需再输入其他内容。
输出格式
共输出 Q 行,每行输出一个字符串或正整数,为对应操作的处理结果,具体含义见题目描述。
输入输出样例
输入:
5
1 lxl 10
2 lxl
3 lxl
2 lxl
4
输出:
OK
10
Deleted successfully
Not found
0
嗯嗯,这道题用map解决会方便很多,因为一是两个值的映射,一个name,一个score,二是用map的函数会简单很多捏。
#include <bits/stdc++.h>
using namespace std;
int main() {
int Q;
cin >> Q;
// 键是学生姓名(string),值是学生成绩(long long)
map<string, long long> students;
while (Q--) {
int op;
string name;
long long score;
cin >> op;
if (op == 1) { // 插入或修改
cin >> name >> score;
// 无论是否存在,直接使用 [] 赋值即可
// 如果存在则更新,不存在则插入
students[name] = score;
cout << "OK\n";
}
else if (op == 2) { // 查询
cin >> name;
auto it = students.find(name);
if (it != students.end()) {//这里是迭代器,就相当于指针,如果找到了,就返回这个位置的迭代器,否则就返回end()
// it->second 访问值(成绩)
cout << it->second << '\n';
} else {
cout << "Not found\n";
}
}
else if (op == 3) { // 删除
cin >> name;
auto it = students.find(name);
if (it != students.end()) {
students.erase(it); // 或 students.erase(name)删除操作
cout << "Deleted successfully\n";
} else {
cout << "Not found\n";
}
}
else { // op == 4,汇总
cout << students.size() << '\n';
}
}
return 0;
}
P5250 【深基17.例5】木材仓库
题目描述
博艾市有一个木材仓库,里面可以存储各种长度的木材,但是保证没有两个木材的长度是相同的。作为仓库负责人,你有时候会进货,有时候会出货,因此需要维护这个库存。有不超过 105 条的操作:
-
进货,格式
1 Length:在仓库中放入一根长度为 Le ngt**h (不超过 109)的木材。如果已经有相同长度的木材那么输出Already Exist。 -
出货,格式
2 Length:从仓库中取出长度为 Le ngt**h 的木材。如果没有刚好长度的木材,取出仓库中存在的和要求长度最接近的木材。如果有多根木材符合要求,取出比较短的一根。输出取出的木材长度。如果仓库是空的,输出Empty。
输入格式
第一行一个数 m 代表操作次数。
接下来 m 行,每行一次操作,格式如题目描述所示。
输出格式
对于每次操作,按照题目描述要求输出答案。
输入输出样例
输入:
7
1 1
1 5
1 3
2 3
2 3
2 3
2 3
输出:
3
1
5
Empty
这道题是用set来解决,也是一个容器,我本来是拿vector写的,确实很麻烦,还是set好。这道题用set主要是满足了这两点,元素自动从小到大排序,元素唯一,不能重复。
#include <bits/stdc++.h>
using namespace std;
int main() {
int m;
cin >> m;
set<long long> warehouse;
while (m--) {
int op;
long long len;
cin >> op >> len;
if (op == 1) { // 进货
if (warehouse.find(len) != warehouse.end()) {
cout << "Already Exist\n";
} else {
warehouse.insert(len);
}
} else { // 出货
if (warehouse.empty()) {
cout << "Empty\n";
continue;
}
auto it = warehouse.lower_bound(len);
long long result;
if (it == warehouse.end()) {
// 没有比 len 大的,取最大的
--it;
result = *it;
warehouse.erase(it);
} else if (it == warehouse.begin()) {
// 最小的都比 len 大或等于,取最小的
result = *it;
warehouse.erase(it);
} else {
// 有两个候选:*it 和 *prev(it)
auto prev_it = prev(it);
long long diff1 = *it - len; // 正数或0
long long diff2 = len - *prev_it; // 正数
if (diff2 <= diff1) {
// 前一个更近或一样近(一样近取短的,即前一个)
result = *prev_it;
warehouse.erase(prev_it);
} else {
result = *it;
warehouse.erase(it);
}
}
cout << result << '\n';
}
}
return 0;
}
set容器
1. set 容器简介
set 是 C++ STL 中的有序集合容器:
-
元素自动从小到大排序
-
元素唯一,不能重复
-
底层通常用红黑树 实现,插入、删除、查找都是 O(log n)
2. 代码中用到的 set 相关操作
warehouse.find(len)
if (warehouse.find(len) != warehouse.end())
-
功能 :在 set 中查找值为
len的元素 -
返回值 :如果找到,返回指向该元素的迭代器 ;如果没找到,返回
end() -
用途 :判断长度为
len的木材是否已存在
warehouse.insert(len)
warehouse.insert(len);
-
功能:向 set 中插入一个元素
-
特点:如果元素已存在,插入失败(但不会报错)
-
返回值 :返回一个
pair<iterator, bool>,其中bool表示是否插入成功
warehouse.empty()
if (warehouse.empty());
-
功能:判断 set 是否为空
-
返回值 :空返回
true,否则false
warehouse.lower_bound(len)
auto it = warehouse.lower_bound(len);
这是本题最关键的函数!
-
功能 :返回第一个不小于
len的元素的迭代器 -
理解 :就是二分查找的
lower_bound -
示例:
-
如果 set 中有
{2, 5, 7, 10},lower_bound(5)返回指向5的迭代器 -
lower_bound(6)返回指向7的迭代器 -
lower_bound(11)返回end()
-
迭代器操作
--it; // 迭代器前移(指向更小的元素)
auto prev_it = prev(it); // 获取前一个迭代器(C++11起可用)
-
迭代器 可以像指针一样使用
++、--操作 -
prev(it)函数返回it的前一个迭代器(需要<iterator>头文件,但<bits/stdc++.h>已包含)
warehouse.erase(it)
warehouse.erase(it);
warehouse.erase(prev_it);
-
功能:删除迭代器指向的元素
-
参数 :可以是迭代器,也可以是具体的值(如
warehouse.erase(len)) -
返回值:返回被删除元素的下一个迭代器(如果用迭代器删除)
3. 其他相关 set 函数(本题没用但常用)
// 上界:第一个大于 len 的元素
auto it = warehouse.upper_bound(len);
// 获取第一个/最后一个元素
auto first = *warehouse.begin();
auto last = *warehouse.rbegin(); // 或 *prev(warehouse.end())
// 清空所有元素
warehouse.clear();
// 获取元素个数
int size = warehouse.size();
map容器
1.map 是什么?
map 是 C++ STL 中的关联容器 ,存储键值对(key-value pair):
-
每个键唯一,不能重复
-
自动按键排序(默认升序)
-
底层用红黑树 实现,插入、删除、查找都是 O(log n)
map<string, long long> students;
// 键类型:string(学生姓名)
// 值类型:long long(学生成绩)
2. 本题用到的 map 操作
students[name] = score
students[name] = score;
-
功能:插入或修改学生成绩
-
特点:
-
如果
name不存在,插入新键值对 -
如果
name已存在,更新对应的值 -
非常方便,一步完成"插入与修改"操作
-
students.find(name)
auto it = students.find(name);
if (it != students.end()) {
// 找到了
}
-
功能 :查找键为
name的元素 -
返回值 :返回指向该元素的迭代器 ;如果没找到,返回
end() -
迭代器 指向一个
pair<const Key, T>,可以通过it->first访问键,it->second访问值
students.erase(it) 或 students.erase(name)
students.erase(it); // 用迭代器删除
students.erase(name); // 直接用键删除
-
功能:删除指定元素
-
两种形式:接受迭代器或键
-
返回值(如果用键删除):返回被删除的元素个数(0 或 1)
students.size()
cout << students.size() << '\n';
-
功能 :返回
map中元素的个数 -
时间复杂度:O(1)
3. map 的其他常用操作(本题没用但常用)
// 判断是否为空
if (students.empty()) { ... }
// 清空所有元素
students.clear();
// 遍历所有学生
for (auto& p : students) {
cout << p.first << ": " << p.second << endl;
}
// 使用迭代器遍历
for (auto it = students.begin(); it != students.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}
// 统计某个键出现的次数(对map来说只能是0或1)
int count = students.count(name);
// 插入的另一种方式
students.insert({name, score});
// 或 students.insert(make_pair(name, score));