文章目录
介绍
本文包括但不限于stl(主要在主页另一篇帖子里),快速幂,二分答案,二分,差分与前缀和,双指针,字符串操作,贪心,各种小细节等等,非常适合前期入门使用。
二分查找模板
函数实现
#include <iostream>
using namespace std;
// 二分查找函数,查找目标值是否存在
int bsearch(int *a, int n, int x) { // 数组a,长度n,目标值x
int l = 1, r = n; // 左边界l,右边界r
while (l <= r) { // 循环直到左右边界重叠或越界
int m = (l + r) / 2; // 中间位置m
if (a[m] == x) return m; // 找到目标值返回下标
else if (a[m] < x) l = m + 1; // 目标值在右区间
else r = m - 1; // 目标值在左区间
}
return -1; // 未找到目标值返回-1
}
int main() {
int n = 6; // 数组长度
int a[] = {0, 2, 4, 6, 8, 10, 12}; // 数组,a[0]占位
int x = 8; // 要查找的目标值
int pos = bsearch(a, n, x); // 调用二分查找函数
if (pos != -1) cout << "Found at: " << pos << endl;
else cout << "Not found" << endl;
return 0;
}
非函数实现
#include <iostream>
using namespace std;
int main() {
int n = 6; // 数组长度
int a[] = {0, 2, 4, 6, 8, 10, 12}; // 数组,a[0]占位
int x = 8; // 要查找的目标值
int l = 1, r = n; // 左边界l,右边界r
int pos = -1; // 存储目标值下标,默认-1表示未找到
while (l <= r) { // 循环直到左右边界重叠或越界
int m = (l + r) / 2; // 中间位置m
if (a[m] == x) { // 找到目标值
pos = m; // 记录下标
break; // 退出循环
} else if (a[m] < x) l = m + 1; // 目标值在右区间
else r = m - 1; // 目标值在左区间
}
if (pos != -1) cout << "Found at: " << pos << endl;
else cout << "Not found" << endl;
return 0;
}
二分答案法经典题目实现形式
二分答案法的步骤:
确定搜索区间:根据题目要求,设定一个可能的答案范围。
条件判断函数:定义一个函数 ceii(mid),判断在当前值 mid 时,是否满足条件。
二分查找:使用二分法在答案空间中查找满足条件的值。
如果 check(mid) 返回 true,说明当前 mid 可能是一个可行解,尝试更大的值。
如果 check(mid) 返回 false,说明当前 mid 不是可行解,尝试更小的值。
#include<bits/stdc++.h>
using namespace std;
int n;
long long c;
const int N = 2e5 + 10;
int a[N];
long long ceii(int m){
long long s = 0;
for(int i = 1;i<=n;i++){
s += (a[i] + 2LL * m) * (a[i] + 2LL * m);
if (s > c) return s;
}
return s;
}
int main() {
cin>>n>>c;
for(int i = 1;i<=n;i++){
cin>>a[i];
}
int l = 1, r = 1e6;
int pos = -1;
while (l <= r) {
int m = (l + r) / 2;
long long s = ceii(m);
if (s == c) {
pos = m;
break;
} else if (s < c) l = m + 1;
else r = m - 1;
}
cout<<pos;
return 0;
}
一维前缀和模板
#include<bits/stdc++.h>
using namespace std;
int main() {
const int N = 1e5 + 10;
int sum[N], a[N];
int n, m;
cin >> n >> m; //n为数组a的长度,m为询问次数
int l, r;
sum[0] = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum[i] = sum[i-1] + a[i];
}
for (int i = 1; i <= m; i++) {
cin >> l >> r; //左区间和右区间
cout << sum[r] - sum[l-1] << endl;//区间[l, r]内元素的和,非下标
}
return 0;
}
一维差分处理区间增量问题
#include <iostream>
using namespace std;
int main() {
const int N = 1e5 + 10; //N表示数组的最大长度
int a[N] = {0}, b[N] = {0};
int n, m; //n为数组a的长度,m为询问次数
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
b[i] = a[i] - a[i - 1]; // 构建差分数组b
}
while (m--) {
int l, r, c; // l 和 r 表示操作的区间 [l, r],c 表示加的值
cin >> l >> r >> c;
b[l] += c;
if (r + 1 <= n) { // 如果 r+1 没有越界
b[r + 1] -= c;
}
}
// 根据差分数组 b 计算最终的数组a
for (int i = 1; i <= n; i++) {
a[i] = a[i - 1] + b[i]; // 根据前缀和恢复原数组a
}
for (int i = 1; i <= n; i++) {
cout << a[i] << " ";
}
cout << endl;
return 0;
}
二维前缀和模板
#include<bits/stdc++.h>
using namespace std;
const int N = 1e9+100;
int s[N][N],a[N][N];
int main() {
int n,m,q;
cin>>n>>m>>q;
for(int i = 1;i<=n;i++){
for(int j =1;j<=m;j++){
cin>>a[i][j];
}
}
for(int i = 1;i<=n;i++){
for(int j =1;j<=m;j++){
s[i][j] = s[i-1][j]+ s[i][j-1]+a[i][j] - s[i-1][j-1];//二维前缀和模板数组
}
}
int x1,y1,x2,y2;
for(int i = 1;i<=q;i++){
cin>>x1>>y1>>x2>>y2;
cout<<s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]<<endl; //求从x1,y1到x2,y2的区域和
}
return 0;
}
二维差分增量模板
#include<bits/stdc++.h>
using namespace std;
const int N = 1000 + 10;
int a[N][N], b[N][N];
int n, m, q;
void insert(int x1, int y1, int x2, int y2, int c) {
b[x1][y1] += c;
b[x2 + 1][y1] -= c;
b[x1][y2 + 1] -= c;
b[x2 + 1][y2 + 1] += c;
}
int main() {
cin >> n >> m >> q;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
b[i][j] = a[i][j] - a[i-1][j] - a[i][j-1] + a[i-1][j-1];
}
}
while (q--) {
int x1, y1, x2, y2, c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
insert(x1, y1, x2, y2, c);
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
a[i][j] = b[i][j] + a[i-1][j] + a[i][j-1] - a[i-1][j-1];
cout << a[i][j] << " ";
}
cout << endl;
}
return 0;
}
其他常用代码片段
遍历所有排列情况
do { } while (next_permutation(v.begin(), v.end()));
清理缓存区
cin.ignore(numeric_limits<streamsize>::max(), '\n');
经典队列操作应用题目
#include<bits/stdc++.h>
using namespace std;
int main() {
int m, n; //m表示缓存最大容量,n表示询问次数
cin >> m >> n; // 读取缓存容量 m 和页面访问次数 n
deque<int> q(m); // 创建一个大小为 m 的双端队列,用来模拟缓存,默认大小为 m
int a; // 用来存储当前访问的页面
int cnt = 0; // 用来记录页面缺失的次数
int qq[1010] = {0}; // 定义一个数组 qq,长度为 1010,初始化所有元素为 0,用来记录哪些页面在缓存中
for (int i = 1; i <= n; i++) { // 遍历所有页面访问
cin >> a; // 读取当前访问的页面号
if (!qq[a]) { // 如果页面 a 不在缓存中
cnt++; // 页面缺失,增加缺页次数
if (q.size() == m) { // 如果缓存已满
int x = q.front(); // 获取队列的第一个元素,即最久未使用的页面
q.pop_front(); // 从队列中移除最久未使用的页面
qq[x] = 0; // 更新数组 qq,标记页面 x 不在缓存中
}
q.push_back(a); // 将当前页面 a 加入队列(缓存)
qq[a] = 1; // 更新数组 qq,标记页面 a 已经在缓存中
}
}
cout << cnt; // 输出缺页次数
return 0; // 程序结束
}
字符串输入
string arr[100];
getline(cin, arr[i]);
自定义排序函数
bool cmp(类型 a, 类型 b) {
// 第一条件: 比较 a 和 b
if (条件1) {
return 排序规则1;
}
// 第二条件: 如果第一条件不满足,比较 a 和 b
if (条件2) {
return 排序规则2;
}
// 第三条件: 如果前面所有条件都不满足,继续比较
if (条件3) {
return 排序规则3;
}
// 默认返回
return 排序规则默认;
}
循环数组索引更新
index = (index + 1) % n; // 因为是循环,使用%运算确保数组循环
解释:
这段代码的含义是在一个固定大小为 n 的数组中循环更新 index,确保它始终保持在合法范围内(0 到 n-1)。它通过取模运算(%)实现循环行为。
具体分析:
1.index + 1:
将当前索引值 index 增加 1,表示向后移动一个位置。
2.% n:
模运算确保索引不会超出数组的边界。如果增加后索引等于或大于 n,模运算会使它“回到”开头。
例如:
2.1 如果 index + 1 = n,则 (index + 1) % n = 0。
2.2 如果 index + 1 = n + 1,则 (index + 1) % n = 1。
3.循环效果:
模运算的结果总是一个小于 n 的非负数,这样可以实现数组的循环访问。
3.1 当 index 为数组最后一个位置(n-1)时,执行 (index + 1) % n 会将索引跳转到第一个位置 0。
3.2 否则,索引会正常向后移动。
注意做题时样例输入陷阱,特殊样例,比如数组长度为0,尤其注意题目给的范围,比如>=
格式化输出
cout << setfill('0') << setw(2) << sum;
cout << fixed << setprecision(2) << num << endl;
输出32位二进制形式
cout << bitset<32>(n) << endl;
cin >> oct >> n; // 从输入以八进制形式读取一个整数
cout << dec << n; // 以十进制形式输出该整数
sawp函数不要忘了使用
注意样例空格
字符串和整数转换
以字符串的形式输入数组进行数字的运算时,字符’0’实际上是48
注意在C++中,字符和整数之间可以进行转换。字符’0’到’9’的ASCII码分别是48到57。当你从一个字符中减去’0’时,实际上是将该字符转换为对应的数字。
注意int long范围,数组可能是double类型等等
字符数组长度
char a[100];
int b = strlen(a);
字符串长度
string a;
int b = a.size();
宏定义
#define 宏名 替换内容
定义尽量都在主函数外定义
类型别名
using ll = long long;
typedef long long ll;
结构体数组示例(贪心排序题可能用到)
#include <iostream>
#include <algorithm> // 包含 sort 函数
using namespace std;
struct Point {
int x, y;
};
// 自定义比较函数
bool cmp(const Point &a, const Point &b) {
if (a.x == b.x) {
return a.y < b.y; // 如果 x 相同,按 y 排序
}
return a.x < b.x; // 否则按 x 排序
}
int main() {
Point points[3];
// 通过 cin 输入结构体数组
for (int i = 0; i < 3; i++) {
cin >> points[i].x >> points[i].y;
}
// 使用自定义比较函数进行排序
sort(points, points + 3, cmp);
// 输出排序后的数组
for (int i = 0; i < 3; ++i) {
cout << "points[" << i << "]: x=" << points[i].x << ", y=" << points[i].y << endl;
}
return 0;
}
lower_bound
和 upper_bound
lower_bound 是 C++ 标准库 中的一个非常有用的函数,它用于在已排序的容器中查找第一个不小于(即大于或等于)给定值的元素的位置。它可以用于数组、vector、deque、set 和 map 等支持随机访问或二叉搜索的数据结构。
(容器中的元素必须是已排序的。lower_bound 使用二分查找,因此只有在排序容器中才能正确工作。)
auto 是 C++11 引入的一个关键字,用于自动推导变量的类型。通过使用 auto,编译器可以根据变量初始化时的值自动推导出该变量的类型。这使得代码更加简洁,特别是在处理复杂的类型时(例如迭代器或类型较长的容器元素)。
#include <iostream>
#include <algorithm>
using namespace std;
vector<int> vec = {1, 3, 3, 5, 7, 9};
// 查找第一个不小于 3 的位置(即第一个 3)
auto lb = lower_bound(vec.begin(), vec.end(), 3);
// 查找第一个大于 3 的位置(即第一个大于 3 的元素位置)
auto ub = upper_bound(vec.begin(), vec.end(), 3);
//lb 的位置是 vec.begin() + 1,即指向第一个 3。
//ub 的位置是 vec.begin() + 3,即指向第一个大于 3 的元素 5。
cout << "lb: " << (lb - vec.begin()) << endl; // 输出 1,表示第一个 3
cout << "ub: " << (ub - vec.begin()) << endl; // 输出 3,表示 5
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
long long a[N], b[N];
int main() {
int n, m, sum;
cin >> n >> m >> sum;
// 输入数组 a[] 和 b[]
for (int i = 0; i < n; i++) {
cin >> a[i];
}
for (int i = 0; i < m; i++) {
cin >> b[i];
}
// 对数组 b[] 进行排序
sort(b, b + m);
// 遍历数组 a[],对于每个 a[i] 使用二分查找
for (int i = 0; i < n; i++) {
long long target = sum - a[i];
// 在 b[] 中查找 target
int idx = lower_bound(b, b + m, target) - b;
// 检查是否找到并且满足 a[i] + b[idx] == sum
if (idx < m && b[idx] == target) {
cout << i << " " << idx << endl;
return 0; // 找到后直接退出
}
}
// 如果没有找到符合条件的 pair
cout << -1 << endl;
return 0;
}
STL 常用函数
最值
max(x, y); // 返回 x 和 y 较大值
min(x, y); // 返回 x 和 y 较小值
排序
sort(va.begin(), va.end(), cmp);
子串操作
#include <iostream>
#include <cstring> // 包含 C 字符串处理函数 strstr
using namespace std;
int main() {
//子串截取
string s = "Hello, World!";
string sub = s.substr(7, 5); // 从下标 7 开始截取 5 个字符
s.erase(7, 10); // 从下标7 删除 10 个字符
//查找子串
size_t pos = s.find("World"); // 查找 "World" 的位置,一般返回第一个字母起始下标
if (pos != string::npos) {
cout << "Found at: " << pos << endl; // 如果找到,输出位置
}
// 定义两个字符数组,用于存储输入的源字符串和需要查找的子串
char str[100]; // 源字符串
char target[100]; // 子串
cin.getline(str, 100); // 使用 getline 读取一整行字符串
cin.getline(target, 100); // 输入需要查找的子串
const char* pos = strstr(str, target); // 使用 strstr 函数查找子串在源字符串中的位置
// 判断是否找到子串
if (pos != nullptr) {
// 如果找到,计算子串的起始下标,并输出
cout << "Substring found at index: " << (pos - str) << endl;
} else {
cout << "Substring not found!" << endl;
}
return 0;
}
双指针模板
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int n, x; // n为数组大小,x为目标和
cin >> n >> x; // 输入n和目标和x
int a[n + 1]; // 数组,从1开始存储
for (int i = 1; i <= n; i++) {
cin >> a[i]; // 输入数组
}
sort(a + 1, a + n + 1); // 排序数组
int i = 1, j = n; // 双指针初始化
while (i < j) {
int s = a[i] + a[j]; // 当前两个数的和
if (s == x) {
cout << a[i] << " " << a[j] << endl;
break;
} else if (s < x) {
i++; // 左指针右移
} else {
j--; // 右指针左移
}
}
if (i >= j) {
cout << "No solution" << endl; // 没有找到满足条件的数对
}
return 0;
}
注意事项补充
不要忘了把oj的编译模式换成C++
一定注意输入的是n的范围还是n个整数的范围!!!!
注意输入样例形式,如1111与1 1 1 1你就要考虑不同的接收输入的方式了
别把2*i写成2i
为防止爆long long ,需对等式右边进行强转
如:s += (a[i] + 2LL * m) * (a[i] + 2LL * m);
定义cmp时,不要带等号
使用auto遍历容器时,要改变容器内元素加&,不改变不加.
continue用法示例
//数字反转,消除原数末尾0,但中间0不动.
int flag = 1;
for(int i = p-1;i>=0;i--){
if(a[i]=='0' && flag==1 && i>0){
continue;
}
flag = 0;
cout<<a[i];
}
字符串经典操作例题(包含回文,去重,取子串)
#include<bits/stdc++.h>
using namespace std;
string s;
string zican(string sub){
string a = "";
a+=sub[0];
for(int i = 0;i<sub.size()-1;i++){
if(sub[i+1]!=sub[i]){
a += sub[i+1];
}
}
return a;
}
bool huiwen(string subb){
int i = 0;
int j = subb.size()-1;
while(i<=j){
if(subb[i]!=subb[j]){
return false;
}
i++;
j--;
}
return true;
}
int main() {
long long sumj = 0;
long long sumo = 0;
cin>>s;
int n = s.size();
for(int i = 0;i<n;i++){
for(int j = 1;i+j<=n;j++){
string sub = s.substr(i,j);
string subb = zican(sub);
if(huiwen(subb)){
if(j%2==0){
sumo++;
}else{
sumj++;
}
}
}
}
cout<<sumo<<" "<<sumj;
return 0;
}
C风格字符串处理
-
int result = strcmp(str1, str2);
strcmp 用于比较两个 C 风格字符串(即 char 数组)
返回 0:如果两个字符串相等。
返回一个负整数:如果 str1 小于 str2(按字典顺序比较)。
返回一个正整数:如果 str1 大于 str2(按字典顺序比较). -
strcpy 用于将一个 C 风格字符串的内容复制到另一个字符串中.
char* strcpy(char* dest, const char* src);
dest:目标字符数组,拷贝的结果会存储在这里。
src:源字符数组,即你要复制的字符串。
返回值:返回目标字符串 dest 的指针。 -
strcat 用于将一个字符串连接到另一个字符串的末尾。
char* strcat(char* dest, const char* src);
dest:目标字符数组,连接结果将存储在此。
src:源字符数组,要追加的字符串。
返回值:返回目标字符串 dest 的指针。 -
strchr 用于查找字符串中第一次出现指定字符的位置。
str:要查找的字符串。
ch:要查找的字符。
返回值:返回指向找到的字符的指针,如果没有找到,则返回 nullptr。
#include <iostream>
#include <cstring>
using namespace std;
int main() {
const char* str = "Hello, world!";
char* pos = strchr(str, 'o'); // 查找字符 'o'
if (pos != nullptr) {
cout << "Found 'o' at position: " << (pos - str) << endl; // 计算相对位置
} else {
cout << "'o' not found!" << endl;
}
return 0;
}
输出:Found ‘o’ at position: 4
- strtok 用于分割字符串,它根据指定的分隔符把一个字符串分解为多个子字符串。
char* strtok(char* str, const char* delimiters);
str:待分割的字符串。首次调用时需要传入原始字符串,后续调用可以传入 nullptr 来继续分割。
delimiters:用于分割的分隔符(多个字符)。
返回值:返回指向子字符串的指针。
#include <iostream>
#include <cstring>
using namespace std;
int main() {
char str[] = "Hello, world, C++!";
char* token = strtok(str, ", "); // 以 ", " 作为分隔符
while (token != nullptr) {
cout << "Token: " << token << endl;
token = strtok(nullptr, ", "); // 继续分割
}
return 0;
}
输出:Token: Hello
Token: world
Token: C++
- strncat 类似于 strcat,但它会限制连接的字符数量,防止溢出
dest:目标字符数组。
src:源字符串。
n:要连接的最大字符数。
快速幂算法
#include <iostream>
using namespace std;
// 快速幂函数:计算 a^b
int qp(int a, int b) {
int r = 1; // r 存储结果,初始为 1
while (b > 0) { // 当指数 b 大于 0 时继续循环
if (b % 2) // 如果 b 是奇数
r *= a; // 将当前基数累乘到结果
a *= a; // 基数自乘
b /= 2; // 指数减半
}
return r; // 返回计算结果
}
int main() {
int a, b;
cout << "输入底数和指数:";
cin >> a >> b; // 输入底数 a 和指数 b
cout << a << "^" << b << " = " << qp(a, b) << endl;
return 0;
}
accumulate
vector<int> vec = {1, 2, 3, 4, 5};
// 计算 vec 中所有元素的和,初始值为 0
int sum = accumulate(vec.begin(), vec.end(), 0);
- 还通过提供自定义的二元操作 op,可以实现不同的聚合操作。例如,计算区间内所有元素的乘积。
vector<int> vec = {1, 2, 3, 4, 5};
// 使用乘法作为操作符计算元素的乘积,初始值为 1
int product = accumulate(vec.begin(), vec.end(), 1, multiplies<int>());
//multiplies<int>() 是 C++ 标准库中提供的一个函数对象,它执行乘法操作。
- accumulate 也可以用于其他类型的数据聚合,例如字符串的拼接。通过传入一个适当的操作函数(如加法运算符),可以将一个字符串序列拼接成一个完整的字符串。
vector<string> words = {"Hello", " ", "World", "!"};
// 使用字符串拼接操作,将所有字符串拼接起来
string result = accumulate(words.begin(), words.end(), string());
- 如果我们想要找出一组元素中的最大值,也可以使用 accumulate,配合自定义的操作函数(如 std::max)。
vector<int> vec = {1, 9, 3, 7, 5};
// 使用 max 来查找最大值
int max_value = accumulate(vec.begin(), vec.end(), vec[0], max<int>());