个人代码库: 蓝桥杯代码仓库

在备考蓝桥杯的过程中,我的编程能力得到了显著提升。通过系统的学习和练习,我接触到了许多之前未曾了解的算法和数据结构,这些知识不仅拓宽了我的技术视野,也为我未来的职业发展奠定了坚实的基础。

第一章:语言基础

2. 竞赛常用库函数

1) sort 对 vector<int>排序用法,自定比较函数

#include<bits/stdc++.h>
using namespace std;

bool cmp(const int& u, const int& v) {
	return u > v;
}

int main() {

	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	// 初始化 v
	vector<int> v = { 5, 1, 3, 9, 11 };

	// 对数组进行排序, 降序排序,默认是升序,所以需要自定义比较函数
	sort(v.begin(), v.end(), cmp);

	// 输出
	for (int i = 0; i < v.size(); i++) { cout << v[i] << ' '; }

	system("pause");
	return 0;
}

2) nth_element 找出第几大的数

#include<bits/stdc++.h>
using namespace std;

int main() {
    // 初始化一个整数向量v,包含一些无序的整数
    vector<int> v = { 5, 1, 7, 3, 10, 18, 9 };
    
    // 使用nth_element函数找出第四大的数
    // nth_element将第4个位置(索引为3)的元素调整到它最终应该在的位置
    // 使得从开始到该位置的所有元素都不大于这个元素,而后面的元素都不小于这个元素
    nth_element(v.begin(), v.begin() + 3, v.end());
    
    // 输出调整后的向量v中的元素
    for (auto& i : v) cout << i << ' ';
    
    // 暂停程序,等待用户操作
    system("pause");
    
    return 0;
}

3) max_element,min_element找出数组的最大值和最小值

#include<bits/stdc++.h> // 包含所有标准库头文件
using namespace std; // 使用标准命名空间
using ll = long long; // 定义长整型别名
const int N = 1e4 + 9; // 定义常量N,值为10009
int a[N]; // 定义数组a,大小为N

int main() {
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 优化输入输出速度
    int n; cin >> n; // 读取整数n
    for (int i = 1; i <= n; i++) cin >> a[i]; // 读取n个整数到数组a中

    // 输出数组a中的最大值和最小值
    cout << *max_element(a + 1, a + 1 + n) << endl;
    cout << *min_element(a + 1, a + 1 + n) << endl;

    ll sum = 0; // 初始化sum为0
    for (int i = 1; i <= n; i++) sum += a[i]; // 计算数组a的元素总和

    // 输出平均值,保留两位小数
    cout << fixed << setprecision(2) << 1.0 * sum / n << endl;
    system("pause"); // 暂停程序,等待用户操作
    return 0; // 返回0,表示程序正常结束
}

4)二分查找binary_search,lower_bound和upper_bound用法和实际应用

#include<bits/stdc++.h>
using namespace std;

int main() {
	vector<int> v = { 5, 1, 8, 3, 10, 18, 9 };
	sort(v.begin(), v.end());
	for (auto& i : v) cout << i << ' ';

	// binary_search
	cout << endl << "please input target: ";
	int target;
	cin >> target;
	bool found = binary_search(v.begin(), v.end(), target);
	if (found) cout << "target found\n";
	else cout << "target not found\n";

	// lower_bound(st, ed, x), 第一个大于等于x的元素地址
	cout << lower_bound(v.begin(), v.end(), 8) - v.begin() << endl;

	// upper_bound(st, ed, x), 第一个大于x的元素地址
	cout << upper_bound(v.begin(), v.end(), 8) - v.begin() << endl;

	// 实际应用,用 upper_bound - lower_bound 可以找出数组中某个元素重复的次数以及所在的区间
	system("pause");
	return 0;
}

5)用lower_bound函数找出数组内的某个数的下标

#include<bits/stdc++.h>
using namespace std;

int main() {
    // 初始化一个大小为200的整数数组data
    int data[200];
    
    // 使用公式4 * i + 6填充数组data,其中i从0到199
    for (int i = 0; i < 200; i++) data[i] = 4 * i + 6;
    
    // 读取用户输入的目标值target
    int target; cin >> target;
    
    // 使用lower_bound函数在数组data中查找第一个不小于target的元素的位置
    // lower_bound返回一个指向该位置的指针,通过减去data的起始地址得到索引
    cout << lower_bound(data, data + 200, target) - data << endl;
    
    // 暂停程序,等待用户操作
    system("pause");
    
    return 0;
}

6)使用库函数实现字母大小写转换

#include<bits/stdc++.h>
using namespace std;
char convertedCh(char ch) {
	if (islower(ch)) ch = toupper(ch);
	else ch = tolower(ch);
	return ch;
}

int main() {
	string s; getline(cin, s);
	for (auto& i : s) i = convertedCh(i);
	cout << s << endl;

	system("pause");
	return 0;
}

7)next_permutation,prev_permutation函数

使用next_permutation,prev_permutation函数生成一个序列的下一个和上一个字典序排列。

#include<bits/stdc++.h>
using namespace std;

int main() {
    // 初始化一个整数向量nums,包含元素1, 2, 3
    vector<int> nums = { 1, 2, 3 };
    cout << "initial permutation: ";
    // 输出初始排列
    for (int num : nums) cout << num << ' ';
    cout << endl;

    // 使用next_permutation函数生成下一个字典序排列
    // next_permutation会将nums重新排列为下一个更大的排列,直到没有更大的排列为止
    while (next_permutation(nums.begin(), nums.end())) {
        cout << "next permutation: ";
        // 输出每次生成的下一个排列
        for (int num : nums) cout << num << " ";
        cout << endl;
    }

    // 初始化另一个整数向量nums2,包含元素3, 2, 1
    vector<int> nums2 = { 3, 2, 1 };
    cout << "initial permutation: ";
    // 输出初始排列
    for (int num : nums2) cout << num << ' ';
    cout << endl;
    // 使用prev_permutation函数生成上一个字典序排列
    // prev_permutation会将nums2重新排列为上一个更小的排列,直到没有更小的排列为止
    while (prev_permutation(nums2.begin(), nums2.end())) {
        cout << "prev permutation: ";
        // 输出每次生成的上一个排列
        for (int num : nums2) cout << num << " ";
        cout << endl;
    }
    // 暂停程序,等待用户操作
    system("pause");
    return 0;
}

/* 输出结果
initial permutation: 1 2 3 
next permutation: 1 3 2 
next permutation: 2 1 3 
next permutation: 2 3 1 
next permutation: 3 1 2 
next permutation: 3 2 1 
initial permutation: 3 2 1 
prev permutation: 3 1 2 
prev permutation: 2 3 1 
prev permutation: 2 1 3 
prev permutation: 1 3 2
prev permutation: 1 2 3
请按任意键继续. . .
*/

8)其他基础常用函数

#include<bits/stdc++.h> // 包含几乎所有标准库头文件,方便快速编写代码,但在实际项目中不推荐使用
using namespace std;

int main() {
    // 声明一个大小为5的整数数组a
	int a[5];
	
    // 使用memset函数将数组a的所有字节设置为0x3f(即二进制的00111111)
	memset(a, 0x3f, sizeof(a));	// 00111111
	
    // 输出数组a中每个元素的二进制表示形式
	for (int i = 0; i < 5; i++) cout << bitset<32>(a[i]) << endl;

	// swap(a,b) 注释掉的代码,用于交换两个变量的值

	// reverse(v.begin(), v.end()) 注释掉的代码,用于反转容器v的元素顺序

	// unique() 去除相邻重复元素,返回去重后范围的尾后迭代器
	// 并不是删除重复元素,而是将重复元素移到后面
	int arr[] = { 1,1,2,2,3 };
	
    // 输出数组arr中的每个元素
	for (int i = 0; i < 5; i++) cout << arr[i];
	cout << endl;
	
    // 使用unique函数去除数组arr中的相邻重复元素,并获取去重后的有效长度n
	int n = unique(arr, arr + 5) - arr;
	
    // 再次输出数组arr中的每个元素,注意此时数组arr中重复元素被移到了后面
	for (int i = 0; i < 5; i++) cout << arr[i];
	cout << endl;
	
    // system("pause") 在Windows系统中暂停程序,等待用户按键继续
	system("pause");
	return 0;
}

3. STL

1)Pair

pair是将2个数据组合成一组数据,当需要这样的需求时就可以使用pair,如stl中的map就是将key和value放在一起来保存。另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。 pair的实现是一个结构体,主要的两个成员变量是first second 因为是使用struct不是class,所以可以直接使用pair的成员变量。

#include<bits/stdc++.h>
using namespace std;
struct Person
{
	string name;
	int age;
};

int main() {
	pair<char, string> p1('a', "hello, world");
	cout << p1.first << " ," << p1.second << endl;
	// pair嵌套使用
	pair<int, pair<int, int>> p2(3, make_pair(4, 5));
	cout << p2.first << ", " << p2.second.first << ", " << p2.second.second << endl;

	// 结合构造体和vector容器一起使用
	vector<Person> people;
	people.push_back({ "wxy",18 });
	people.push_back({ "lb",19 });
	people.push_back({ "zf",28 });

	for (auto& i : people) cout << "name: " << i.name << ", "  << "age: " << i.age << endl;

	vector<pair<Person, int>> sourse;
	sourse.push_back({ people[0],81 });
	sourse.push_back({ people[1],89 });
	sourse.push_back({ people[2],99 });

	for (auto& i : sourse) cout << "name: " << i.first.name << ", age: " << i.first.age << ", sourse: " << i.second << endl;

	system("pause");
	return 0;
}

2)Vector

是一个动态数组容器,属于标准模板库(STL)的一部分。它提供了一种灵活且高效的方式来存储和操作一组元素,具有动态大小调整、连续内存存储、随机访问和迭代访问等特点。通过下标运算符[]或at()方法可以随机访问元素,使用迭代器或范围for循环可以遍历元素。vector支持添加、删除、插入和清空等操作,并能够自动管理内存,当需要更多空间时会自动重新分配内存并复制原有元素 详细见

常用函数:获取长度 size(); 末尾元素添加 push_back();末尾元素删除 pop_back();指定位置插入元素insert() ;指定位置删除元素erase();检查是否为空函数 empty(); 调整大小函数 resize()。还可以使用 begin(),end()函数遍历元素(获取的是初始或末尾元素的迭代器)

#include<bits/stdc++.h>
using namespace std;

int main() {
	vector<int> numbers = { 5,2,8,5,1,2,9,8 };
	// 输出原始容器
	for (const auto& number : numbers) cout << number << " ";
	cout << endl;
	// 末尾新增元素
	numbers.push_back(6);
	// 容器排序
	sort(numbers.begin(), numbers.end());
	// 输出排序后的容器
	for (const auto& number : numbers) cout << number << " ";
	cout << endl;
	// 去除重复元素,unique(numbers.begin(), numbers.end())返回的是去重后的重复集合的首地址
	numbers.erase(unique(numbers.begin(), numbers.end()), numbers.end());
	// 输出去重后的容器
	for (const auto& number : numbers) cout << number << " ";
	cout << endl;

	// 想容器中插入元素, 在指定位置插入元素,该位置原有的元素整体往后移
	numbers.insert(numbers.begin() + 2, 3);
	// 输出插入元素后的容器
	for (const auto& number : numbers) cout << number << " ";
	cout << endl;
	system("pause");
	return 0;
}

3)List

是一种双向链表容器,属于标准模板库(STL)的一部分。它提供了一种灵活且高效的方式来存储和操作一组元素,具有动态大小调整、双向迭代器、随机访问和迭代访问等特点。通过迭代器可以遍历元素,使用front()和back()方法可以访问链表的首尾元素。list支持添加、删除、插入和清空等操作,并能够自动管理内存,当需要更多空间时会自动重新分配内存并复制原有元素。详细见

List常用函数。由于是双向的迭代器,所以相比于vector多了几个对头部操作的函数,其他的基本一致

#include<bits/stdc++.h>
using namespace std;

int main() {
	cout << "用得少,基本用不到,作为了解学习\n";

	// 尾部插入元素
	list<int> mylist;
	mylist.push_back(1);
	mylist.push_back(2);
	mylist.push_back(3);

	// 链表头部插入元素
	mylist.push_front(0);

	// 遍历输出
	for(int num:mylist){
		cout << num << endl;
	}
	
	system("pause");
	return 0;
}

4)Stack

stack是一个标准模板库(STL)容器适配器,用于实现栈数据结构。它提供了一种后进先出(LIFO)的数据管理方式。stack支持基本的栈操作,如压入元素(push)、弹出元素(pop)、访问栈顶元素(top)、检查栈是否为空(empty)以及获取栈的大小(size)。它通常用于需要按特定顺序处理元素的场景,例如函数调用、表达式求值和深度优先搜索等算法。注意:stack是STL中唯一一个后进先出的迭代器,其他都是先进先出(FIFO)且 stack 不能遍历

小tips:如果将一个数组的元素依次放入栈,再依次取出,则可以将数组翻转

常用函数

代码示例:

#include<bits/stdc++.h>
using namespace std;

int main() {
	cout << "仅做了解即可\n";

	stack<int> myStack;

	// 插入元素
	myStack.push(10);
	myStack.push(20);
	myStack.push(30);
	myStack.push(40);

	// 获取元素
	cout << "栈顶元素" << myStack.top() << endl;

	// 弹出栈顶元素 
	myStack.pop();

	// 弹出栈顶元素后再获取栈顶元素
	cout << "弹出第一个元素后的栈顶元素为: " << myStack.top() << endl;

	// 检查栈是否为空 
	if(myStack.empty()) cout << "栈为空" << endl;
	else cout << "栈不为空" << endl;

	// 获取栈的大小 
	cout << "栈的大小为: " << myStack.size() << endl;

	system("pause");
	return 0;
}

5)Queue

1、queue

常用函数,注意:queue只能操作头部和尾部的元素,无法操作中间的元素

代码示例

#include<bits/stdc++.h>
using namespace std;

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int m; cin >> m;
	queue<string> V, N;
	while (m--) {
		string op; cin >> op;
		if (op == "IN") {
			string name, q; cin >> name >> q;
			if (q == "V") V.push(name);
			else N.push(name);
		}
		else {
			string q; cin >> q;
			if (q == "V") V.pop();
			else N.pop();
		}
	}
	
	// 输出测试,
	cout << "输出结果: " << endl;
	while (V.size()) {
		cout << V.front() << endl;
		V.pop();
	}

	while (N.size()) {
		cout << N.front() << endl;
		N.pop();
	}

	system("pause");
	return 0;
}
/* 输入样例 输出 M 次操作后 VIP 窗口队列和普通窗口队列中的姓名(从头到尾),先输出 VIP 窗口队列后输出普通窗口队列。

5
IN xiaoming N
IN Adel V
IN laozhao N
OUT N
IN CLZ V

*/

2、priority_queue

priority_queue 与普通队列不同,priority_queue中的元素是按照一定的优先级进行排序
默认情况下,priority_queue按照元素的值从大到小进行排序,即最大元素位于队列的前面
当然也可以自定义比较函数实现从小到大

优先级队列是一个十分重要的数据结构,考察评率极高,下面是优先队列常用函数

代码示例:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n; cin >> n;
	// priority_queue 优先队列,自动排序,默认大的在栈顶
	priority_queue<ll, vector<ll>, greater<ll>> pq;  // greater<ll>自带的重载,实现小根堆,小的在栈顶
	for (int i = 1; i <= n; i++) {
		ll x; cin >> x;
		pq.push(x);
	}

	ll ans = 0;
	while (pq.size() >= 2) {
		// 取出两个小栈顶,并弹出
		ll x = pq.top(); pq.pop();
		ll y = pq.top(); pq.pop();

		ans += x + y;  // 将两个小栈顶相加再推入优先队列
		pq.push(x + y);
	}
	cout << ans << endl;
	system("pause");
	return 0;
}
// 关于优先队列详解见 https://blog.csdn.net/qq_42978535/article/details/142697224

3、deque

deque双端队列可以实现两端进行高效的插入和删除(两端能进能出)

常用函数: deque可以实现对中间元素进行操作,当然用的比较少,仅做了解即可

6)Set

1、set集合

set是一种容器,用于存储一组唯一的元素,并按照一定的排序规则进行排序。set中的元素默认是按照升序排序的,默认情况下它使用元素的比较运算符(<)来进行排序。注意:set的元素都是唯一的,不允许重复元素存在,当插入一个重复元素时,set会自动忽略该元素

常用函数示例:

代码示例(修改set比较方法的常见手段)

#include<bits/stdc++.h>
using namespace std;


struct MyCompare
{
    bool operator()(const int& a, const int& b) const {
        // 自定义比较逻辑
         return a > b;  // 改为降序
    }
};

int main() {
	// set<int> mySet;	// 默认升序

	set<int, greater<int>> mySet;	// 使用自带的greater改为降序
	set<int, MyCompare> mySet2;	// 在 mySet2里面自定义比较函数MyCompare

	mySet.insert(25); 	mySet2.insert(25); 
	mySet.insert(17); 	mySet2.insert(17);
	mySet.insert(39); 	mySet2.insert(39);
	mySet.insert(42); 	mySet2.insert(42);

	for(const auto& elem : mySet) cout << elem << " ";
	cout << endl;
	for(const auto& elem2 : mySet2) cout << elem2 << " ";

	system("pause");
	return 0;
}

set常用操作

#include<bits/stdc++.h>
using namespace std;
 
int main() {
	set<int> myset;

    // 插入元素
    myset.insert(5);
    myset.insert(2);
    myset.insert(8);
    myset.insert(2); // 重复元素
     
    // 遍历集合 
    cout << "set element: ";
    for(const auto& elem : myset) cout << elem << " ";
    cout << endl;

    // 查找元素
    int target = 5;
    auto it = myset.find(target); // 找到则返回该元素迭代器,找不到则返回end()

    if(it != myset.end()) cout << target << " found in the set. " << endl;
    else cout << target << " not found in the set. " << endl;

    // 移除元素
    int remValue = 2;
    myset.erase(remValue);

    // 再次遍历
    for(const auto& elem : myset) cout << elem << " ";
    cout << endl;

    // 清空集合
    myset.clear();

    // 判断集合是否为空 
    if(myset.empty()) cout << "set is empty" << endl;
    else cout << "set is not empty" << endl;

	system("pause");
	return 0;
}

2、multiset多重集合

大体与set类似,不同之处在于multiset容器允许存储相同的元素

常用函数,注意: erase() 函数,由于存在相同的元素,所以在删除某个元素时,需不需将容器内与其等值的元素全部删除,可以使用 st.erase(st.find(x)) 来实现删除单个元素

multiset常见操作示例

#include<bits/stdc++.h>
using namespace std;
 
int main() {
	multiset<int> myset;

    // 插入元素
    myset.insert(5);
    myset.insert(2);
    myset.insert(8);
    myset.insert(2); // 重复元素
     
    // 遍历集合 
    cout << "set element: ";
    for(const auto& elem : myset) cout << elem << " ";
    cout << endl;

    // 查找元素
    int target = 5;
    auto it = myset.find(target); // 找到则返回该元素迭代器,找不到则返回end()

    if(it != myset.end()) cout << target << " found in the set. " << endl;
    else cout << target << " not found in the set. " << endl;

    // 移除元素
    int remValue = 2;
    // myset.erase(remValue); // 删除所有的2
    myset.erase(myset.find(2));  // 删除单个2

    // 再次遍历
    for(const auto& elem : myset) cout << elem << " ";
    cout << endl;

    // 清空集合
    myset.clear();

    // 判断集合是否为空 
    if(myset.empty()) cout << "set is empty" << endl;
    else cout << "set is not empty" << endl;

	system("pause");
	return 0;
}

3、unordered_set 无序集合

unordered_set是一种容器,用于存储一组唯一的元素,并且没有特定的顺序。unordered set容器使用哈希表来实现元素的存储和访问,因此元素的插入、删除和査找的时间复杂度都是常数时间,即0(1),当然,这个其实并不绝对,最坏也可能到 O(n)。所以一般情况下,很少使用 无序集合

7)Map

1、map

map是一种关联容器,用于存储一组键值对(key-value pairs),其中每个键(key)都是唯一的。
map容器根据键来自动进行排序,并且可以通过键快速查找对应的值
map容器使用红黑树(Red-Black Tree)数据结构来实现,具有较快的插入、删除和査找操作的时间复杂度0(logn) map需要重点掌握

常用函数

代码示例

#include<bits/stdc++.h>
using namespace std;
 
int main() {
    map<int, string> mymap = {{1,"Apple"}, {2, "Banana"}, {3, "Orange"}};
    
    // 插入元素
    mymap.insert(make_pair(4, "Xiaomi"));
    mymap.insert({5, "H3C"});

    // 遍历并打印map中的元素
    for(const auto& pair : mymap) 
        cout << "Key: " << pair.first << ", Value: " << pair.second << endl;
    
    // 删除元素
    mymap.erase(3);

    // 判断元素是否存在 (key为3存不存在)
    if(mymap.count(3) == 0) cout << "key 3 not found" << endl;
    else cout << "key 3 founded" << endl;

    // 清空元素
    mymap.clear();

    // 判断 map 是否为空
    if(mymap.empty()) cout << "map is empty" << endl;
    else cout << "map is not empty";
    
	system("pause");
	return 0;
}

2、multimap

相比于map,不同之处在于可以存在多个相同的键 multimap几乎不用

代码示例:

#include<bits/stdc++.h>
using namespace std;
 
int main() {
    multimap<int, string> mymap = {{1,"Apple"}, {2, "Banana"}, {2, "Orange"}};
    
    // 插入元素
    mymap.insert(make_pair(3, "Xiaomi"));
    mymap.insert({3, "H3C"});

    // 查找和访问元素
    auto range = mymap.equal_range(2);
    for(auto it = range.first; it != range.second; it++){
        cout << "key: " << it->first << ", value: " << it->second << endl;
    }

    // 遍历并打印map中的元素
    cout << "------------------" << endl;
    for(const auto& pair : mymap) 
        cout << "Key: " << pair.first << ", Value: " << pair.second << endl;
    
    // 删除元素
    mymap.erase(mymap.find(3));    // 只会删除一个,如果直接写3则删除所有key为3的键值对

    // 判断元素是否存在 (key为3存不存在)
    if(mymap.count(3) == 0) cout << "key 3 not found" << endl;
    else cout << "key 3 founded" << endl;

    // 清空元素
    mymap.clear();

    // 判断 map 是否为空
    if(mymap.empty()) cout << "map is empty" << endl;
    else cout << "map is not empty";
    
	system("pause");
	return 0;
}

3、unordered_map

无序,插入删除速度更快,但这个一般不用(复杂度不稳定)

8)例题

1、考察优先队列 priority_queue

#include<bits/stdc++.h>
using ll = long long;
using namespace std;

// 小蓝吃糖果
int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n; cin >> n;
	priority_queue<int> pq;
	ll sum = 0;
	for (int i = 1; i <= n; i++) {
		int x; cin >> x;
		pq.push(x);
		sum += x;
	}
	ll mx = pq.top();

	if (sum - mx >= mx - 1) cout << "Yes" << '\n';
	else cout << "No" << '\n';

	return 0;
}
/*
不能连续两次吃一种,为了消除种类最多的糖果,可以采用下面隔一个吃最多的那种
可以发现规律, 把4消耗完,中间需要缓冲的次数为 4 - 1,其他种类总数大于或者等于3即可存在刚好吃完的场景

4 2 1   1
3 2 1   2
3 1 1   1
2 1 1   2
2 0 1   1
1 0 1   3
1 0 0   1
0 0 0

4 3 1  1
3 3 1  2
3 2 1  1
2 2 1  2
2 1 1  1
1 1 1  2
1 0 1  1
0 0 1  3
0 0 0

4 3 3   1
3 3 3   2
3 2 3   1
2 2 3   3
2 2 2   1
1 2 2   2
1 1 2   3
1 1 1   1
0 1 1   2
0 0 1   3
0 0 0
*/

2、考察栈 stack,小蓝的括号串

#include<bits/stdc++.h>
using namespace std;
const int N = 105;
stack<char> stk;

char s[N];

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	int n; cin >> n;
	cin >> s + 1;
	bool ans = true;
	for (int i = 1; i <= n; i++) {
		if (s[i] == '(') stk.push(s[i]);
		else {
			if (stk.size()) stk.pop();
			else ans = false;
		}
	}
	if (stk.size()) ans = false;
	cout << (ans ? "Yes" : "No") << endl;
	system("pause");
	return 0;
}

3、1531 快递分拣,考察 map vector

#include<bits/stdc++.h>
using namespace std;

map<string, vector<string>> mp;
vector<string> citys;

int main() {
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	int n; cin >> n;
	for (int i = 1; i <= n; i++) {
		string a, b; cin >> a >> b;
		if (!mp.count(b)) citys.push_back(b);
		mp[b].push_back(a);
	}

	cout << "-----------------------------------------------" << endl;
	
	for (const auto& city : citys) {
		cout << city << ' ' << mp[city].size() << endl;
		for (const auto& i : mp[city]) cout << i << endl;
	}
	system("pause");
	return 0;
}

第二章 基础语法

1、基础算法

1. 枚举

枚举算法是一种基本的算法思想,它通过穷举所有可能的情况来解决问题。它的基本思想是将问题的解空间中的每个可能的解都枚举出来,并进行验证和比较,找到满足问题条件的最优解或者所有解。

枚举算法适用于问题规模较小、解空间可穷举的情况。它的优点是简单直观,不需要复杂的数学推导,易于实现。但是,由于需要穷举所有可能的情况,对于问题规模较大的情况,枚举算法的时间复杂度可能会非常高,效率较低。

1. 找超半数

// 在 n×m 的矩阵中,找出超半数 https://www.lanqiao.cn/problems/3227/learning/
#include<bits/stdc++.h>
using namespace std;

map<int, int> mp;

int main(){
    ios::sync_with_stdio(0),cout.tie(0),cin.tie(0);
    int m,n;
    cin >> m >> n;

    for(int i = 0; i < m * n; i++){
        int x; cin >> x;
        mp[x]++;
    }
    for(const auto& p : mp){
        if(p.second * 2 > m * n ) cout << p.first << endl;
    }
    return 0;
}

/*

3 3
1 2 3
2 2 2
1 2 2

*/

2. 模拟

1.模拟算法通过模拟实际情况来解决问题,容易理解但实现复杂,需要注意细节。 2.模拟题不涉及太难的算法,由许多简单的部分组成,考察选手的细心程度和逻辑思维。 3.为了逻辑清晰,常使用小函数来帮助解题,如整数和字符串的相互转化、日期和特殊条件的判断。 4.模拟算法不卡时间复杂度,主要考察细心程度。

1、扫雷问题

// 扫雷 https://www.lanqiao.cn/problems/549/learning/
#include<bits/stdc++.h>
using namespace std;
const int N = 150;
int mp[N][N], ans[N][N];

int main(){
    int n, m; cin >> n >> m;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            cin >> mp[i][j];
        }
    }

    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            if(mp[i][j] == 1){
                ans[i][j] = 9;
                continue;
            }

            for(int _i = max(1, i - 1); _i <= min(n, i + 1); _i++){
                for(int _j = max(1, j - 1); _j <= min(m, j + 1); _j++){
                    if(mp[_i][_j]) ans[i][j]++;
                }
            }
        }
    }

    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= m; j++){
            cout << ans[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}
/*  测试用例
3 4
0 1 0 0
1 0 1 0
0 0 1 0
*/

2、灌溉问题

// https://www.lanqiao.cn/problems/551/learning/ 灌溉问题
#include<bits/stdc++.h>
using namespace std;
const int N = 120;
bool a[N][N], b[N][N];

int main() {
	int n, m; cin >> n >> m;
    int t; cin >> t;
    for(int i = 1; i <= t; i++){
        int x,y; cin >> x >> y;
        a[x][y] = 1;
    }
    int k; cin >> k;
    while(k--){
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                // 为什么要再开一个b ???
                if(a[i][j]) b[i][j] = b[i][j-1] = b[i][j+1] = b[i-1][j] = b[i+1][j] = 1;
                // if(a[i][j]) a[i][j-1] = a[i][j+1] = a[i-1][j] = a[i+1][j] = 1;  // error
            }
        }
        // 将b复制回a
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                a[i][j] = b[i][j];
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                if(a[i][j]) ans++;
            }
        }
	cout << ans << endl;
	return 0;
}

3、回文日期问题

2、排序

3、省赛技巧与复习

基础复习完后,以刷题为主,后续可能不再更新这篇笔记

算法小记

1. 离散数组

// 离散化数组
#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 9;
int a[N];
vector<int> L;

int getidx(int x){  // 找元素下标
    return lower_bound(L.begin(), L.end(), x) - L.begin();
}

int main() {
    int n; cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++) L.push_back(a[i]);

    sort(L.begin(), L.end());   // 排序
    L.erase(unique(L.begin(), L.end()), L.end());   // 去重

    cout << "离散化数组为: ";
    for(const auto &i : L) cout << i << ' ';
    cout << "\n";

    int v; cin >> v;
    cout << getidx(v) << "\n";
	return 0;
}
/* test samples
7
108 312 21321 3213 8888 343 8888
 */

2. DFS(回溯法模版)

#include<bits/stdc++.h>
using namespace std;

const int N = 1e2 + 10; 
int a[N];               // 存储排列结果
bool vis[N];            // 标记数字是否被访问过
int n;                  // 排列的大小

// 回溯函数,dep表示当前处理到第几个数字
void dfs(int dep) {
    if (dep == n + 1) { // 如果已经处理完所有数字
        for (int i = 1; i <= n; i++) { // 输出排列
            cout << a[i] << " \n"[i==n];
        }
        return;
    }

    for (int i = 1; i <= n; i++) { // 尝试将1到n的每个数字放入当前位置
        if (vis[i]) continue;      // 如果该数字已经被使用,跳过
        vis[i] = true;             // 标记该数字为已使用
        a[dep] = i;                // 将数字放入当前位置
        dfs(dep + 1);              // 递归处理下一个位置
        vis[i] = false;            // 回溯,恢复状态
    }
}

int main() {
    cin >> n; // 输入排列的大小
    dfs(1);   // 从第一个位置开始递归
    return 0;
}

2.1 回溯法经典样题1

// lanqiao1508 N皇后问题
#include<bits/stdc++.h>
using namespace std;
const int N  = 15;
int n, ans; 
int vis[N][N];

void dfs(int dep){
    if(dep == n + 1){
        ans++;
        return;
    }
    for(int i = 1; i <= n; i++){    // 为什么下面只标记同一行,因为每次搜寻时只会在同一行(深度)寻找一个皇后
        if(vis[dep][i]) continue;       // 所以只需要排除某行某个元素的同一列即可
        // 修改状态
        for(int _i = 1; _i <= n; _i++) vis[_i][i]++;    // 标记同一列
        // 表壳 X 形模版
        for(int _i = dep, _j = i; _i >= 1 && _j >= 1; _i--, _j--) vis[_i][_j]++;    // 标记左上对角线
        for(int _i = dep, _j = i; _i <= n && _j >= 1; _i++, _j--) vis[_i][_j]++;    // 标记右上对角线   
        for(int _i = dep, _j = i; _i >= 1 && _j <= n; _i--, _j++) vis[_i][_j]++;    // 标记左下对角线
        for(int _i = dep, _j = i; _i <= n && _j <= n; _i++, _j++) vis[_i][_j]++;    // 标记右下对角线

        // 搜查下一深度
        dfs(dep + 1);

        // 恢复现场
        for(int _i = 1; _i <= n; _i++) vis[_i][i]--;
        for(int _i = dep, _j = i; _i >= 1 && _j >= 1; _i--, _j--) vis[_i][_j]--;
        for(int _i = dep, _j = i; _i <= n && _j >= 1; _i++, _j--) vis[_i][_j]--;
        for(int _i = dep, _j = i; _i >= 1 && _j <= n; _i--, _j++) vis[_i][_j]--;
        for(int _i = dep, _j = i; _i <= n && _j <= n; _i++, _j++) vis[_i][_j]--;
    }
}

int main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    dfs(1);
    cout << ans << '\n';
    return 0;
}
/* test samples
5
*/

2.2回溯法经典样体2

// lanqiao182 小朋友崇拜圈
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, a[N], dfn[N], idx, mindfn;

int dfs(int x){
    dfn[x] = ++idx;
    if(dfn[a[x]]){
        if(dfn[a[x]] >= mindfn) return dfn[x] - dfn[a[x]] + 1;
        return 0;
    }
    return dfs(a[x]);
}

int main(){
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    int ans = 0;
    for(int i = 1; i <= n; i++){
        if(!dfn[i]){
            mindfn = idx + 1;
            ans = max(ans, dfs(i));
        }
    }
    cout << ans << endl;
    return 0;
}
/* test samples 
9
3 4 2 5 3 8 4 6 9
*/

2.2回溯法经典样体3

// lanqiao178 全球变暖
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
char mp[N][N];
int n, scc, col[N][N];
bool vis[N*N];
// 移动向量
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};

void dfs(int x, int y){
    col[x][y] = scc;
    for(int i = 0; i < 4; i++){ 
        int nx = x + dx[i], ny = y + dy[i];
        if( col[nx][ny] || mp[nx][ny] == '.') continue; // 不需要判断nx, ny是否在地图里面,因为地图边缘都是海洋
        dfs(nx ,ny);
    }
}

int main(){
    ios::sync_with_stdio, cin.tie(0), cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> mp[i] + 1;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            if(col[i][j] || mp[i][j] == '.') continue;
            scc++;  // scc表示当前颜色编号
            dfs(i, j);
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            if(mp[i][j] == '.') continue;
            bool tag = true;
            for(int k = 0; k < 4; k++){
                int x = i + dx[k], y = j + dy[k];
                if(mp[x][y] == '.') tag = false;
            }
            if(tag){
                if(!vis[col[i][j]]) ans++;
                vis[col[i][j]] = true;
            }
        }
    }
    cout << scc - ans << '\n';
    return 0;
}
/* test samples
7
.......
.##....
.##....
....##.
..####.
...###.
.......
*/

3. DFS剪枝