基础知识

1. 前言

算法参考资料:

  1. OI Wiki
  2. AcWing算法基础课
  3. ACM算法竞赛代码模板(长期更新) - lightmon - 博客园
  4. ACM竞赛模版 - dreaming2019 - 博客园
  5. 繁凡的ACM模板(满注释模板)-CSDN博客
  6. EdisonBa博客园
  7. ACM算法模板(C++)--适用于ACM,蓝桥杯算法比赛_acm模板-CSDN博客
  8. ACM模板大全-CSDN博客
  9. ACM算法模板总结(分类详细版)_acm竞赛常用算法模板-CSDN博客
  10. fanfansann/PDF-version-of-the-blog-fanfansann: 我的所有优质博客全部开源啦(我自己原创的《ACM模板》《算法全家桶》《算法竞赛中的初等数论》 PDF免费下载
  11. 《算法竞赛》(罗永军)
  12. 《算法竞赛进阶指南》(李煜东)

库函数参考资料:
cplusplus.com/reference/

学习方法:

  1. 多刷题补题,洛谷、Atcoder、Codeforce、vjudge、leetcode、poj、pta 等
  2. 记模板,形成自己的理解
  3. 常反思,多总结,悟一悟

2. 头文件

#include <bits/stdc++.h>
using namespace std;
typedef unsigned int uint;
#define int long long
#define ll long long
// #define int __int128
typedef unsigned long long ull;
using ld = long double;
inline int read();
inline void write(int);
#define inf 0x3f3f3f3f         // 1061109567
#define INF 0x3f3f3f3f3f3f3f3f // 4557430888798830399
#define lowbit(x) ((x) & (-x))
#define all(x) x.begin(), x.end()
#define sz(x) (int)(x).size()
#define pb push_back
#define m_p make_pair
#define m_t make_tuple
#define pii pair<int, int>
#define PII pair<long long, long long>
typedef tuple<int, int, int> tiii;
typedef tuple<long long, long long, long long> TIII;
typedef vector<int> vi;
typedef vector<pii> vpii;
#define fi first
#define se second
#define mem(x, y) memset(x, y, sizeof(x))
#define endl '\n'
const long double PI = acos(-1.0);
#define eps 1e-7
#define rep(i, s, t) for (int i = (s); i <= (t); i++)
#define per(i, s, t) for (int i = (s); i >= (t); i--)
#define debug(x) cerr << #x << ": " << x << endl;
#define Timeok ((double)clock() / CLOCKS_PER_SEC < MAX_TIME)
const double MAX_TIME = 1.0 - 0.0032;
const int dir4[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
const int dir8[8][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};
const int maxn = 1e6 + 5;
const int mod = 998244353;
const int MOD = mod;
const int N = 1011;

mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
int randint(int l, int r)
{
    return uniform_int_distribution<int>(l, r)(rng);
}

void solve()
{
    
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    // cout << fixed;
    // cout.precision(18);
    // cout << fixed << setprecision(10);
    int T = 1;
    // cin >> T;
    while (T--)
        solve();
    return 0;
}

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }

    while (isdigit(ch))
    {
        x = (x << 1) + (x << 3) + (ch ^ '0');
        ch = getchar();
    }
    return x * f;
}

inline void write(int x)
{
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
    return;
}

附:随机数生成器使用指南

在竞赛编程中,生成高质量的随机数非常重要。下面详细解释如何使用我们模板中的随机数生成器:

// 初始化随机数引擎(只需声明一次)
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

// 生成区间 [l, r] 内的随机整数
int randint(int l, int r) {
    return uniform_int_distribution<int>(l, r)(rng);
}
  • 基本用法
// 生成1到100之间的随机整数
int random_num = randint(1, 100);
cout << "随机数: " << random_num << endl;
  • 常见应用场景
  1. 生成随机数组
int n = 10;
vector<int> arr(n);
for (int i = 0; i < n; i++) {
    arr[i] = randint(1, 100); // 1到100之间的随机数
}
  1. 生成随机字符串
int len = 8;
string s(len, ' ');
for (int i = 0; i < len; i++) {
    s[i] = 'a' + randint(0, 25); // 随机小写字母
}
  1. 生成随机图
int n = 100; // 节点数
int m = 200; // 边数
vector<pair<int, int>> edges;
for (int i = 0; i < m; i++)
{
    int u = randint(1, n);
    int v = randint(1, n);
    if (u != v) 
    {
        edges.push_back({u, v});
    }
}
  1. 生成随机树
int n = 100; // 节点数
vector<int> parent(n+1);
for (int i = 2; i <= n; i++) {
    parent[i] = randint(1, i-1); // 确保树结构
}
  1. 随机打乱数组
vector<int> arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
shuffle(arr.begin(), arr.end(), rng); // 使用rng引擎打乱
  • 高级用法
  1. 生成随机实数
// 生成 [0.0, 1.0) 之间的随机实数
double rand01() {
    return generate_canonical<double, 10>(rng);
}

// 生成 [l, r) 之间的随机实数
double randreal(double l, double r) {
    return uniform_real_distribution<double>(l, r)(rng);
}
  1. 随机选择元素
// 从非空容器中随机选择一个元素
template<typename T>
T choice(const vector<T>& v) {
    return v[randint(0, v.size()-1)];
}
  1. 生成随机排列
vector<int> random_permutation(int n)
{
    vector<int> perm(n);
    iota(perm.begin(), perm.end(), 1); // 1,2,3,...,n
    shuffle(perm.begin(), perm.end(), rng);
    return perm;
}
  • 使用示例
void solve()
{
    // 生成5个随机数
    cout << "5个随机数 (1-100): ";
    for (int i = 0; i < 5; i++) {
        cout << randint(1, 100) << " ";
    }
    cout << endl;
    
    // 生成随机字符串
    string s = random_string(10);
    cout << "随机字符串: " << s << endl;
    
    // 生成随机图
    auto edges = random_graph(5, 7);
    cout << "随机图边集: ";
    for (auto [u, v] : edges) {
        cout << "(" << u << "," << v << ") ";
    }
    cout << endl;
    
    // 生成随机树
    auto tree = random_tree(5);
    cout << "随机树边集: ";
    for (auto [u, v] : tree) {
        cout << "(" << u << "," << v << ") ";
    }
    cout << endl;
}

// 辅助函数实现
string random_string(int len) {
    string s(len, ' ');
    for (int i = 0; i < len; i++) {
        s[i] = 'a' + randint(0, 25);
    }
    return s;
}

vector<pair<int, int>> random_graph(int n, int m) {
    vector<pair<int, int>> edges;
    set<pair<int, int>> edge_set;
    
    while (edges.size() < m) {
        int u = randint(1, n);
        int v = randint(1, n);
        if (u != v && !edge_set.count({min(u, v), max(u, v)})) {
            edges.push_back({u, v});
            edge_set.insert({min(u, v), max(u, v)});
        }
    }
    return edges;
}

vector<pair<int, int>> random_tree(int n) {
    vector<int> parent(n+1);
    vector<pair<int, int>> edges;
    
    for (int i = 2; i <= n; i++) {
        parent[i] = randint(1, i-1);
        edges.push_back({parent[i], i});
    }
    return edges;
}

3. 小知识点

1. 位运算乘除

x=x*(2^n)可以转化成 x<<n
x=x/(2^n)可以转化成 x>>n

2. for

for (register int i(1); i <= n; i++)

3. 短函数前加 inline

4. 判断奇偶

if(a % 2 == 0)  可以转化成  if((a & 1) == 0)

5. 取模用 &

x = 667 % 4;    可以转化成  x = 667 & (4-1);
x = 667 % 32;   可以转化成  x = 667 & (32-1);

6. 正负转换用位运算

i = -i;       可以转化成  i = ~i + 1;   或   i = (i ^ -1) + 1; 

7. 取绝对值

k = abs(x);   可以转化成  k = (x ^ (x >> 31))-(x >> 31);  

8. 离线处理

先读取所有查询,然后统一处理,计算结束后一起输出

9. 四舍五入

double a;
a = (int)(a + 0.5);

10. pow

pow () 函数用来求 x 的 y 次幂,x、y 及函数值实际上为 double 型,其在使用中的原型为:double pow (double x, double y);

pow () 直接输出会出现 3e5 这种缩写形式,所以做题时要用 (long long) pow (), 让其变成一串数字,而不是缩写

11. long double

%Lf

12. 存图

// 存储点与点的关系
map<int, int> mp[n + 1];
// vector<vector<int>> mp(n + 1, vector<int>(n + 1)); 会爆

13. 修改值

a[x] 赋值为 p, a[x] += p - a[x];
a[x] 乘上 p,   a[x] += a[x] * p - a[x]

4. STL 模板

1. vector

#include <vector>

vector<int> v;
vector<vector<int> > a(k + 1, vector<int>(n + 1));	//a[k + 1][n + 1]
vector G(N, vector(N, 0));	// vector G(N, vector<int>(N, 0))			//G[N][N]
 
/* 操作 */
v.clear();  // 清空
v.swap(v1); // 和另一个 vector 进行交换,常数复杂度
 
v.push_back(k);     // 从末尾插入
v.emplace_back(k);  // 从末尾插入,更快
v.pop_back();       // 弹出末尾元素
 
v.insert(pos, k);   // 在 pos 之前插入 k
v.emplace(pos, k);   // 同上面的 insert,更快,但只能插入一个元素
v.insert(pos, n, k); // 在 pos 之前插入 n 个 k
v.insert(pos, v1.begin(), v1.end()); // 在 pos 之前插入另一个 vector 中的一段(左闭右开)[ )
v.insert(pos, {1, 2, 3, 4, 5});  // 显而易见
v.erase(pos);        // 删除 pos 指的元素
v.erase(pos1, pos2);   // 删除 [pos1, pos2) 的元素

/* 查询 */
v[k];       // 访问第 k 个元素
v.front();  // 返回首元素
v.back();   // 返回尾元素
v.begin();  // 返回首元素的迭代器
v.end();    // 返回数组尾端占位符的迭代器(空)
v.empty();  // 返回 vector 是否为空
v.size();   // 返回 vector 元素个数
 
/* 遍历 */
for(int i = 0; i < v.size(); ++i)
    v[i];

v.insert(upper_bound(v.begin(), v.end(), t), t);	//插入时进行排序

2. queue

先进先出

#include<queue>
 
queue<int> q; 
// priority_queue<int> q(从大到小排序);

q.empty();      // 判断队列是否为空
q.size();       // 返回队列长度
q.push(item);   // 对于 queue,在队尾压入一个新元素
                // 对于 priority_queue,在基于优先级的适当位置插入新元素
q.pop();        // 弹出队首元素
 
// queue only:
q.front();      //返回队首元素的值,但不删除该元素
q.back();       //返回队尾元素的值,但不删除该元素

// priority_queue only:
q.top();        //返回具有最高优先级的元素值,但不删除该元素

deque

双端队列

与 queue 其他一样,仅有以下区别:
deque<int> q;
q.push_front();
q.push_back();
q.pop_front();
q.pop_back();

priority_queue

优先队列

// 默认大根堆,实现由大到小排序
priority_queue<int, vector<int>, less<int> > p;
// 小根堆,实现由小到大排序
priority_queue<int, vector<int>, greater<int>> p;

int a[]= {10,60,50,20};
priority_queue<int> q1;
priority_queue<int> q2(a, a + 4);
priority_queue<int, vector<int>, greater<int> > q3(a, a + 4);

q.empty();
q.top();
q.emplace();
q.push();
q.pop();
q1.swap(q2);
自定义比较函数的方法
在C++中,有多种方法可以自定义优先队列的比较函数:

方法一:使用结构体重载运算符()
通过创建一个结构体并重载_()_运算符,可以定义一个函数对象来作为比较函数。例如:
struct Compare {
	bool operator()(int a, int b) {
		return a > b; // 定义小顶堆
	}
};
priority_queue<int, vector<int>, Compare> pq;

方法二:使用类重载运算符()
与结构体类似,也可以通过类来重载_()_运算符。不过需要注意的是,类中的运算符重载函数需要声明为public_:

class Compare {
public:
	bool operator()(int a, int b) {
		return a > b; // 定义小顶堆
	}
};
priority_queue<int, vector<int>, Compare> pq;

方法三:使用函数指针
可以定义一个比较函数,并使用_decltype_关键字来获取该函数的类型,然后将函数指针传递给优先队列:

bool compare(int a, int b) {
	return a > b; // 定义小顶堆
} 
priority_queue<int, vector<int>, decltype(&compare)> pq(compare);

方法四:使用Lambda表达式
Lambda表达式提供了一种便捷的方式来定义匿名函数对象,可以直接在优先队列的定义中使用:

auto cmp = [](int a, int b) {
	return a > b; // 定义小顶堆
};
priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);

方法五:使用std::function包装Lambda表达式
如果需要更多的灵活性,可以使用_std::function_来包装Lambda表达式,然后将其作为比较函数:

#include <functional>
std::function<bool(int, int)> cmp = [](int a, int b) {
	return a > b; // 定义小顶堆
};
priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);

3. string

#include<cstring>
string s1,s2;

s1 + s2;    // 将两个字符串拼接
[cur];      // 访问下标
s1.size();  // 返回字符串长度

s1.append(str);          	// 将 str 添加到 s1 末尾

// 将从 pos 位置开始 n 个字符的子串替换为 str
s1.replace(pos, n, str); 	
// 将以 first 开始(含)、last 结束(不含)的子串替换为 str,其中 first 和 last 均为迭代器
s1.replace(first, last, str);

// 删除从 pos 开始(含)的 n 个字符 (若不传参给 n 则表示删去 pos 位置及以后的所有字符)
s1.erase(pos, n);

s1.insert(pos, str);     	// 在 pos 位置插入字符串 str
s1.insert(pos, n, str) 		// 在 pos 处连续插入 n 次字符串 str

// 从pos位置开始截取一个长度为 len 的字符串 (如果从 pos 开始的后缀长度不足 len 则截取这个后缀)
s1.substr(pos, len);

// 查找字符串中一个字符/字符串在 pos(含)之后第一次出现的位置(若不传参给 pos 则默认为 0 )
s1.find(str, pos);			// 找不到则返回 -1

// 从末尾开始,查找并返回第一个找到的字符 ch 的位置
s1.rfind(ch);           	// 找不到则返回 -1

stoi, stol, sto
从字符串中转换整数、长整数或浮点数。

int num = stoi("123");
double val = stod("123.456");

to_string
将数值转换为字符串。

string str = to_string(123);

4. stack

先进后出

#include<set>
stack<int> s; 
stack<int, vector<int> > stk;  // 覆盖基础容器类型,使用vector实现stk
s.empty();      // 判断 stack 是否为空,为空返回 true,否则返回 false
s.size();       // 返回 stack 中元素的个数
s.pop();        // 删除栈顶元素,但不返回其值
s.top();        // 返回栈顶元素的值,但不删除此元素
s.push(item);   // 在栈顶压入新元素 item

5. map

#include<map>
map<int, string> m;// int 是 key,string 是 value。

// 基本操作
m.size();  
m.empty(); 
m.clear();
m.count(k);

// 插入
m[1] = "one"; // 如果键不存在,会创建新元素
m[2] = "two"; // 如果键存在,会更新值
m.insert({3, "three"}); // 插入一个键值对
m.insert(pair<int, string>(4, "four"));
m.insert(make_pair(5, "five"));
m.emplace(5, "five"); // 效率比 insert 略高,避免不必要的拷贝

// 修改
m[1] = "XCPC";
m.find(1)->second = ...;

// 查找 
m[1];
m.at(1);
m.find(1)->second;

auto it = m.find(3);
if (it != m.end())
    cout << it->first << ": " << it->second << endl;
else
    cout << "Key not found" << endl;

// 删除
m.erase(2); // 删除键为 2 的元素 //删除键为 k 的元素
m.erase(it); // 删除迭代器指向的元素

// 遍历
它会按照 key 排序
for(auto it = mp.begin(); it != mp.end(); ++it) 
    cout << it->second << ' ';

for (const auto& [key, value] : m)
    cout << key << ": " << value << endl;

// 其他函数
auto lb = m.lower_bound(2); // 返回第一个键 >= 2 的迭代器 
auto ub = m.upper_bound(2); // 返回第一个键 > 2 的迭代器
auto range = m.equal_range(2); // 返回一对迭代器,分别为 lower_bound 和 upper_bound
m1.swap(m2);

// 自定义比较函数
struct Compare {
    bool operator()(const int& a, const int& b) const {
        return a > b; // 降序排列
    }
};

map<int, string, Compare> m3 = {{1, "one"}, {2, "two"}, {3, "three"}};
for (const auto& [key, value] : m3)
    cout << key << ": " << value << endl;

multimap

#include <map>
multimap<int, string> mp; // 可重

mp.size(), mp.empty(), mp.clear(); // 常规操作
mp.count(k) // 找 key 为 k 的个数
mp.find(k)  // 返回第一个插入的 k 的迭代器
mp.erase(k) // 删除所有键值为 k 的元素

/* 插入 */
mp.insert(make_pair(int, string)); // 只能用 make_pair 构造键值对

/* 修改 */
m.find(k)->second = ...; // 修改第一个插入的 key 为 k 的
for(auto it = mp.find(k); it->first == k; ++it) // 修改 key 为 k,值为自己选的
    if(it->second == "I") it->second = "Love"; 

/* 查找 */
mp.find(k)->second;

/* 遍历 */
for(auto it = mp.begin(); it != mp.end(); ++it) 
    cout << it->second << ' ';

unordered_map

#include<unordered_map>
用法:与 map 差别不大。
优点:因为内部实现了哈希表,因此其查找速度非常的快
缺点:哈希表的建立比较耗费时间
适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用 unordered_map

6. set

自动从小到大排序,自动去重
所有操作的时间复杂度为 O (log n),因为 set 是基于平衡二叉树(红黑树)实现的。

#include<set>

//初始化
set<int> s;		// multiset<int> s (不去重)
set<int, greater<int> > s; // 降序排序
set<int> s(s2);
set<int> s = {1, 2, 3};
set<int> s(pos1, pos2);

// 迭代器
set<int>::iterator it; 

// 插入
s.insert(k);			   
s.insert({9, 10, 11});
s.insert(pos1, pos2);	//插入[pos1, pos2)内的元素

//删除
s.erase(k); 	// 删除值为 k 的元素
s.erase(it); 	// 删除迭代器 it 指向的元素
s.erase(pos1, pos2); 	// 删除[pos1, pos2)内的元素

//查找
s.find(k);     	// 查找某元素 k ,找到则返回其迭代器,否则返回 s.end()
s.count(k);    	// 返回某个值元素 k 的个数
s.contains(k);	//(C++20 引入):更简洁的判断值是否存在	
s.contains({1, 2, 3})//(C++23引入):可以检查一个 initializer_list 或一个范围的所有元素是否存在

//查询
s.lower_bound(k);//返回第一个 >= 给定值 k 的迭代器
s.upper_bound(k);//返回第一个 >  给定值 k 的迭代器
s.equal_range(k);//返回等于给定值 k 的范围(对于 set 而言,最多返回一个元素的范围)

s.emplace(k); // 类似于 insert(k)
s.emplace_hint(pos1, k); // 提示位置为 pos1

s.merge(s2);	//(C++17 引入):将另一个 set(s2) 的元素合并到当前 set(s) 中,重复元素会被忽略

s.empty();    // 判断 set 是否为空,为空返回 true,否则返回 false
s.clear();    // 清除所有元素

s.rbegin();
s.rend();
s.begin();    // 返回指向第一个元素的迭代器
--s.end();    // 返回指向最后一个元素的迭代器
*s.begin();   // 返回指向第一个元素的值
*--s.end();   // 返回指向最后一个元素的值
              // 区间形式为 [ begin , end ) ,所以 end 要自减
s.size();     // 返回集合中元素的个数

              
//支持 == 、!= 、< 、 >  等操作符

/* 遍历 */
for(it = s.begin() ; it != s.end() ; ++it)
    *it; // 使用迭代器遍历 

set 的默认比较器是 <,这意味着元素必须支持 < 运算符。如果你需要自定义排序规则,需要提供一个比较器函数或仿函数。

struct Compare {
    bool operator()(const int &a, const int &b) const {
        return a > b;  // 降序排序
    }
};

std::set<int, Compare> s;

unordered_set

不排序,O (1) 查找。

#include <unordered_set>
// #include <unordered_multiset>
unordered_set<ll> s;
// unordered_multiser<ll> s;

s.insert(); // 在开头插入某元素
s.find();   // 查找,返回迭代器
s.count();  // 返回某个值元素的个数

/* 遍历 */
for(iter = s.begin() ; iter != s.end() ; ++iter)
    *iter; 
// 注意:新插入的元素遍历时先被扫到。

7. list

#include <list>
list<int> l, l2;
list<int>::iterator it;
 
/* 操作 */
l.clear();      // 清空
l.insert(it, 0);// 在迭代器前面插入一个元素,迭代器指向原来的元素
l.erase(it);    // 删除迭代器指向的元素
l.remove(0);    // 在 list 删除某一种元素(全删)
l.push_back(0); // 在 list 的末尾添加一个元素   
l.push_front(0);// 在 list 的头部添加一个元素 
l.pop_back();   // 删除最后一个元素 
l.pop_front();  // 删除第一个元素 
l.merge(l2);    // 合并两个 list 
l.reverse();    // 把 list 的元素倒转 
l.sort();       // 给 list 排序 
l.swap(l2);     // 交换两个 list 
l.unique();     // 删除 list 中重复的元素
 
/* 查询 */
l.begin();      // 返回指向第一个元素的迭代器 
l.end();        // 返回末尾的迭代器 
l.front();      // 返回第一个元素 
l.back();       // 返回最后一个元素 
l.empty();      // 如果 list 是空的则返回 1
l.size();       // 返回 list 中的元素个数 
 
/* 遍历 */
for(it = l.begin(); it != l.end(); ++it)
    *it;

8. bitset

#include <bitset>

bitset<100> b;
bitset<100> f;

b = 10, f = 11;
b[0]; // 访问某一位

bitset(): 每一位都是 false
bitset(unsigned long val): 设为 val 的二进制形式
bitset(const string& str): 设为 01串 str

/* 相同大小的 bitset 可以进行运算符操作 */
/* == != & &= | |= ^ ^= ~ << <<= >> >>=  */

b.count();  // 返回 1 的数量
b.size();   // 返回 bitset 的大小
b.any();    // 若有 1, 返回 1
b.none();   // 若无 1, 返回 1
b.all();    // 若全是 1, 返回 1
b.set();    // 全部设为 1
b.set(pos, val);// 将第 pos 位设为 val (0/1)
b.reset();  	// 全部设为 0
b.reset(pos)	// 将pos位设置成 false
b.flip();   	// 翻转每一位
b.flip(pos);  	// 翻转 pos 位
b.to_string();	// 返回转换成的字符串表达
b.to_ulong(); 	// 返回转换成的 unsigned long 表达
b.to_ullong() 	// 返回转换成的 unsigned long long 表达
b._Find_first();// 返回 bitset 第一个 true 的下标,若没有 true 则返回 bitset 的大小
b._Find_next(pos);// 返回 pos 后面(下标严格大于 pos 的位置)第一个 true 的下标,若 pos 后面没有 true 则返回 bitset 的大小

9. rope

rope 的复制操作是 O (log n) 的,可以较轻松地实现可持久化。
想要使用 rope,需要在头文件中加入两行:

#include <ext/rope>
using namespace __gnu_cxx;

定义字符串类型的 rope,叫做 crope,要这样定义:

crope a;

支持的操作:

a.push_back(x);  // 在 a 的末尾添加字符串 x
a.insert(k, x);  // 在 a 的第 k 个字符后加入字符串 x
a.erase(k, x);   // 在 a 的第 k 个字符后删除 x 个字符
a.replace(k, x); // 将 a 的第 k 个字符后 x 的长度个字符删除,并插入 x
a.substr(k, x);  // 获取 a 的第 k 个字符后的长度为 x 的字符串
a.at(k);         // 获取 a 的第 k 个字符(从 0 开始)

10. Acwing

vector, 变长数组,倍增的思想
    size()  返回元素个数
    empty()  返回是否为空
    clear()  清空
    front()/back()
    push_back()/pop_back()
    begin()/end()
    []
    支持比较运算,按字典序

pair<int, int>
    first, 第一个元素
    second, 第二个元素
    支持比较运算,以first为第一关键字,以second为第二关键字(字典序)

string,字符串
    size()/length()  返回字符串长度
    empty()
    clear()
    substr(起始下标,(子串长度))  返回子串
    c_str()  返回字符串所在字符数组的起始地址

queue, 队列
    size()
    empty()
    push()  向队尾插入一个元素
    front()  返回队头元素
    back()  返回队尾元素
    pop()  弹出队头元素

priority_queue, 优先队列,默认是大根堆
    size()
    empty()
    push()  插入一个元素
    top()  返回堆顶元素
    pop()  弹出堆顶元素
    定义成小根堆的方式:priority_queue<int, vector<int>, greater<int>> q;

stack, 栈
    size()
    empty()
    push()  向栈顶插入一个元素
    top()  返回栈顶元素
    pop()  弹出栈顶元素

deque, 双端队列
    size()
    empty()
    clear()
    front()/back()
    push_back()/pop_back()
    push_front()/pop_front()
    begin()/end()
    []

set, map, multiset, multimap, 基于平衡二叉树(红黑树),动态维护有序序列
    size()
    empty()
    clear()
    begin()/end()
    ++, -- 返回前驱和后继,时间复杂度 O(logn)

    set/multiset
        insert()  插入一个数
        find()  查找一个数
        count()  返回某一个数的个数
        erase()
            (1) 输入是一个数x,删除所有x   O(k + logn)
            (2) 输入一个迭代器,删除这个迭代器
        lower_bound()/upper_bound()
            lower_bound(x)  返回大于等于x的最小的数的迭代器
            upper_bound(x)  返回大于x的最小的数的迭代器
    map/multimap
        insert()  插入的数是一个pair
        erase()  输入的参数是pair或者迭代器
        find()
        []  注意multimap不支持此操作。 时间复杂度是 O(logn)
        lower_bound()/upper_bound()

unordered_set, unordered_map, unordered_multiset, unordered_multimap, 哈希表
    和上面类似,增删改查的时间复杂度是 O(1)
    不支持 lower_bound()/upper_bound(), 迭代器的++,--

bitset, 圧位
    bitset<10000> s;
    ~, &, |, ^
    >>, <<
    ==, !=
    []

    count()  返回有多少个1

    any()  判断是否至少有一个1
    none()  判断是否全为0

    set()  把所有位置成1
    set(k, v)  将第k位变成v
    reset()  把所有位变成0
    flip()  等价于~
    flip(k) 把第k位取反

5. 常用算法函数

C++ 提供了丰富的算法函数库,主要通过头文件 <algorithm><numeric> 来提供常用的算法函数

1. 排序算法

sort

对范围内的元素进行排序,时间复杂度为 \((O (\frac{N}{logN}))\)

sort(vec.begin(), vec.end());
sort (a.begin (), a.end ());//less<int>()		//1 2 3 4 5
sort (a.begin (), a.end (), greater<int>());	//5 4 3 2 1
 
bool cmp (...)
{
	...
}
sort (a.begin (), a.end (), cmp);
 
//lambda表达式
sort(a.begin(), a.end(), [](int x,int y){return x>y;} );
原本应该是这样的:
sort(a.begin(), a.end(), [](int x,int y) -> bool {return x>y;} );
//C++可以自动识别函数返回值得数据类型,所以可以简写
struct cri
{
    int a, b, v;
    bool operator<(const cri &x) const
    {
        return v < x.v;
    }
} a[N];
sort (a + 1, a + n + 1);

struct cri
{
    int a, b, v;
    bool operator>(const cri &x) const
    {
        return v > x.v;
    }
} a[N];
sort (a + 1, a + n + 1, greater<cri>());

stable_sort

稳定排序,保持相等元素的相对位置。

stable_sort(vec.begin(), vec.end());

partial_sort

对一部分元素排序。

将 begin 和 end 范围内的元素排序,使得 begin 到 begin+k 范围内的元素都比 begin+k 指向的元素小,begin+k 到 end 范围内的元素都比 begin+k 指向的元素大

partial_sort(vec.begin(), vec.begin() + k, vec.end(), cmp);
bool cmp(int a, int b)
{
    return a > b;
}

int main() 
{
    vector<int> v = { 4, 2, 9, 1, 5 };
    partial_sort(v.begin(), v.begin() + 3, v.end(), cmp);
    for (int i : v)
        cout << i << ' ';	//95412
}

nth_element

对第 k 个位置的元素进行重排,使得它左边的所有元素都比它小,右边的都比它大。

函数只是把下标为 k 的元素放在了正确位置,对其它元素并没有排序,当然 k 左边元素都小于等于它,右边元素都大于等于它,所以可以利用这个函数快速定位某个元素

nth_element(vec.begin(), vec.begin() + k, vec.end(), cmp);

is_heap

判断范围是否是堆。

bool result = is_heap(vec.begin(), vec.end());

make_heap

将范围内的元素调整为堆。

make_heap(vec.begin(), vec.end());

push_heap

将新元素插入堆中(堆尾元素)。

vec.push_back(new_elem);
push_heap(vec.begin(), vec.end());

pop_heap

弹出堆顶元素。

pop_heap(vec.begin(), vec.end());
vec.pop_back(); // 删除堆顶

sort_heap

将堆排序。

sort_heap(vec.begin(), vec.end());

2. 查找算法

find

查找范围内第一个等于指定值的元素,返回迭代器。

auto it = std::find(vec.begin(), vec.end(), value);

二分查找,前提是范围已排序。

bool found = binary_search(vec.begin(), vec.end(), value);

lower_bound

返回第一个大于等于某值的元素的迭代器,前提是范围已排序。

auto it = lower_bound(vec.begin(), vec.end(), value);

upper_bound

返回第一个大于某值的元素的迭代器,前提是范围已排序。

auto it = upper_bound(vec.begin(), vec.end(), value);

any_of

判断范围内是否至少有一个元素满足给定条件。

bool result = any_of(vec.begin(), vec.end(), [](int x) { return x > 10; });

all_of

判断范围内是否所有元素都满足给定条件。

bool result = all_of(vec.begin(), vec.end(), [](int x) { return x > 0; });

none_of

判断范围内是否没有任何元素满足给定条件。

bool result = none_of(vec.begin(), vec.end(), [](int x) { return x < 0; });

在一个范围中搜索另一个范围。

auto it = search(vec1.begin(), vec1.end(), vec2.begin(), vec2.end());

search_n

搜索 n 个连续的等于某值的元素。

auto it = search_n(vec.begin(), vec.end(), 3, value);

find_end

在一个范围中查找另一个范围最后一次出现的位置。

auto it = find_end(vec1.begin(), vec1.end(), vec2.begin(), vec2.end());

find_first_of

查找范围中第一次出现的等于另一个范围中任意元素的元素。

auto it = find_first_of(vec1.begin(), vec1.end(), vec2.begin(), vec2.end());

3. 最大最小值算法

max

max({-1, 0, 1, 9})

max_element

返回范围内的最大元素的迭代器。

auto it = max_element(vec.begin(), vec.end());

min_element

返回范围内的最小元素的迭代器。

auto it = min_element(vec.begin(), vec.end());

minmax

同时返回两个值中的最小值和最大值(C++11 中引入)。

auto result = minmax(a, b);

minmax_element

同时返回范围内的最小和最大元素的迭代器。

auto result = std::minmax_element(vec.begin(), vec.end());

4. 变换与修改算法

reverse

反转范围内的元素。

reverse(vec.begin(), vec.end());

rotate

将范围内的元素循环移动。

rotate(vec.begin(), vec.begin() + k, vec.end());

next_permutation

生成字典序的下一个排列。

next_permutation(vec.begin(), vec.end());

prev_permutation

生成字典序的上一个排列。

prev_permutation(vec.begin(), vec.end());

is_permutation

判断两个范围是否为相同元素的不同排列。

bool result = is_permutation(vec1.begin(), vec1.end(), vec2.begin());

lexicographical_compare

按字典顺序比较两个范围,判断第一个范围是否小于第二个范围。

bool result = lexicographical_compare(vec1.begin(), vec1.end(), vec2.begin(), vec2.end());

unique

删除相邻重复元素,返回不重复的最后一个元素的下一位地址。

auto it = unique(vec.begin(), vec.end());

5. 数学算法

__int 128

int 类型范围约为 1e9,long long 的数据范围约为 1e18,如果题目的数据范围超过 long long 的限度 (例如 long long 乘 long long 时可能爆 long long),就要考虑使用高精度。

这是 128 字节的数据类型,可以支持的数据范围大约在 2 的 127 次幂左右,不过由于该数据类型不在 C++ 标准中,所以只支持四则运算功能,无法直接用 cin, cout 进行输入输出(输入输出类似于 string 类型),想要使用 int 128 还需要抄一份输入输出的板子。

  • 注意由于 int 128 的输入输出利用的是 getchar (), putchar () 等函数,因此使用 int 128 时不能关闭同步流。
__int128 read()
{
    __int128 x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
            f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

void write(__int128 x)
{
    if(x < 0)
    {
        putchar('-');
        x = -x;
    }
    if(x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}

accumulate

求和,定义在 <numeric> 头文件中。

#include <numeric>

accumulate(起始迭代器, 结束迭代器, 初始值, 自定义操作函数)

//计算数组中所有元素的和
int res = accumulate(arr.begin(), arr.end(), 0);	

//计算数组中每个元素乘以3之后的和
int func(int acc, int num) 
{
	return acc + num * 3;
}
int res = accumulate(arr.begin(), arr.end(), 0, func);


//计算数组中所有元素的乘积
int res = accumulate(arr.begin(), arr.end(), 1, multiplies<int>());

//拼接字符串
vector<string> words{"this ", "is ", "a ", "sentence!"};
string init = "hello, ", res;	//init表示字符串的初始值
res = accumulate(words.begin(), words.end(), init);  // hello, this is a sentence!    

partial_sum

计算部分和,并将结果存储到另一个范围中。

partial_sum(vec.begin(), vec.end(), result.begin());

exclusive_scan

(C++17): 计算前缀和,但不包含当前元素。

exclusive_scan(vec.begin(), vec.end(), result.begin(), 0);

inclusive_scan

(C++17): 计算前缀和,包含当前元素。

inclusive_scan(vec.begin(), vec.end(), result.begin());

gcd

计算两个数的最大公约数,C++17 中引入。

int result = gcd(a, b);

lcm

计算两个数的最小公倍数,C++17 中引入。

int result = lcm(a, b);

6. 集合操作

set_union

计算两个有序集合的并集。

vector<int> result;
set_union(vec1.begin(), vec1.end(), vec2.begin(), vec2.end(), inserter(result));

set_intersection

计算两个有序集合的交集。

vector<int> result;
set_intersection(vec1.begin(), vec1.end(), vec2.begin(), vec2.end(), inserter(result));

set_difference

计算两个有序集合的差集。

vector<int> result;
set_difference(vec1.begin(), vec1.end(), vec2.begin(), vec2.end(), std::back_inserter(result));

set_symmetric_difference

计算两个有序集合的对称差集。

vector<int> result;
set_symmetric_difference(vec1.begin(), vec1.end(), vec2.begin(), vec2.end(), inserter(result));

merge

合并两个已排序的范围,结果也保持有序。

vector<int> result(vec1.size() + vec2.size());
merge(vec1.begin(), vec1.end(), vec2.begin(), vec2.end(), result.begin());

includes

判断一个有序集合是否包含另一个有序集合。

bool result = std::includes(vec1.begin(), vec1.end(), vec2.begin(), vec2.end());

inplace_merge

合并两个已排序的范围,使得整个范围也保持有序。通常用于在部分排序后合并数据。

inplace_merge(vec.begin(), vec_middle, vec.end());

7. 比较和查找相关的算法

equal

判断两个范围内的元素是否相等。

bool result = equal(vec1.begin(), vec1.end(), vec2.begin());

mismatch

找出两个范围之间第一个不相等的元素。

auto result = mismatch(vec1.begin(), vec1.end(), vec2.begin());

lexicographical_compare

字典序比较两个范围的元素。

bool result = lexicographical_compare(vec1.begin(), vec1.end(), vec2.begin(), vec2.end());

is_sorted

判断范围内的元素是否已排序。

bool sorted = is_sorted(vec.begin(), vec.end());

is_sorted_until

返回指向第一个未排序元素的迭代器。

auto it = is_sorted_until(vec.begin(), vec.end());

is_partitioned

判断范围内的元素是否被划分为满足某个谓词和不满足谓词的两部分。

bool part = is_partitioned(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; });

8. 删除元素相关的算法

remove

删除等于某值的元素,将其移动到范围末尾并返回新范围的结束迭代器。需要结合 erase 完成删除操作。

auto new_end = remove(vec.begin(), vec.end(), value);
vec.erase(new_end, vec.end());

remove_if

删除满足条件的元素。

auto new_end = remove_if(vec.begin(), vec.end(), [](int x) { return x < 0; });
vec.erase(new_end, vec.end());

unique

移除连续的重复元素,只保留一个副本。

auto it = unique(vec.begin(), vec.end());
vec.erase(it, vec.end());

unique_copy

移除连续的重复元素,并将结果复制到另一个范围。

unique_copy(vec.begin(), vec.end(), result.begin());

9. 分区相关算法

partition

将范围内的元素按照谓词进行划分,满足谓词的元素移到前面,不满足的移到后面。

partition(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; });

stable_partition

partition 类似,但保证划分后各元素的相对顺序不变。

stable_partition(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; });

partition_copy

复制并分区,将满足条件的元素复制到一个范围,不满足条件的复制到另一个范围。

partition_copy(vec.begin(), vec.end(), even.begin(), odd.begin(), [](int x) { return x % 2 == 0; });

10. 聚合函数

adjacent_difference

计算相邻元素之间的差值,并将结果存储到另一个范围中,定义在 <numeric> 中。

adjacent_difference(vec.begin(), vec.end(), result.begin());

inner_product

计算两个范围的内积,定义在 <numeric> 中。

int result = inner_product(vec1.begin(), vec1.end(), vec2.begin(), 0);

partial_sum

计算部分和,定义在 <numeric> 中。

partial_sum(vec.begin(), vec.end(), result.begin());

iota

生成递增序列,定义在 <numeric> 中。

iota(vec.begin(), vec.end(), start_value);
#include <iostream>
#include <numeric>   	//iota头文件
using namespace std;
void func()
{
    vector<int> v(10);
    iota(v.begin(),v.end(),1);
    vector<int>::iterator it = v.begin();
    while(it != v.end())
    {
        cout<<*it++<<" ";
    }
}

结果:
1	2	3	4	5	6	7	8	9	10
  • 定义在 numeric 头文件中的 iota () 函数模板会用连续的 T 类型值填充序列。前两个参数是定义序列的正向迭代器,第三个参数是初始的 T 值。第三个指定的值会被保存到序列的第一个元素中。保存在第一个元素后的值是通过对前面的值运用自增运算符得到的。当然,这意味着 T 类型必须支持 operator++()。下面展示了如何生成一个有连续的浮点值元素的 vector 容器:
std::vector<double> data(9);
double initial {-4};
std::iota (std::begin (data) , std::end (data) , initial);
std::copy(std::begin(data), std::end(data),std::ostream_iterator<double>{std::cout<< std::fixed << std::setprecision(1), " "});
std::cout << std::endl;  
// -4.0 -3.0 -2.0 -1.0 0.0 1.0 2.0 3.0 4.0
#include <iostream>
#include <numeric>
#include <list>

int main() 
{
    std::list<char> chars(4);

    std::iota(chars.begin(), chars.end(), 'A');

    for (const auto& ch : chars) 
        std::cout << ch << " ";
        
    std::cout << std::endl;

    return 0;
}

atoi ()

#include <stdlib.h> 
int atoi(const char *str);
  • 功能:atoi () 会扫描 str 字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或字符串结束符 (’\0’) 才结束转换,并将结果返回返回值。
  • 参数:
    Str:待转换的字符串
    【返回值】返回转换后的整型数;如果 str 不能转换成 int 或者 str 为空字符串,那么将返回 0
void func()
{
    char str1[] = "-10";
    int num1 = atoi(str1);
    printf("num1 = %d\n", num1);
}

//结果: 
num1 = -10

而 char str1[] = "abc-1100def";结果是: num1 = 0

11. 旋转与翻转相关的算法

rotate

将范围内的元素旋转,即左移或右移指定的元素个数。

rotate(vec.begin(), vec.begin() + 1, vec.end());
// 将 vec 的第一个元素移到最后,其他元素向前移动一位

rotate_copy

旋转范围内的元素,并将结果复制到另一个范围。

vector<int> result(vec.size());
rotate_copy(vec.begin(), vec.begin() + 1, vec.end(), result.begin());

reverse

反转范围内的元素顺序。

reverse(vec.begin(), vec.end());

reverse_copy

反转范围内的元素顺序,并将结果复制到另一个范围。

reverse_copy(vec.begin(), vec.end(), result.begin());

12. 移动与复制相关的算法

move

将一个范围内的元素移动到另一个范围(避免复制),常用于处理右值引用。

move(vec1.begin(), vec1.end(), vec2.begin());

move_backward

将一个范围内的元素移动到另一个范围,元素的顺序反向拷贝。

move_backward(vec1.begin(), vec1.end(), vec2.end());

copy

将一个范围的元素复制到另一个范围。

copy(vec1.begin(), vec1.end(), vec2.begin());

copy_if

将满足条件的元素从一个范围复制到另一个范围。

copy_if(vec.begin(), vec.end(), result.begin(), [](int x) { return x % 2 == 0; });

copy_backward

将范围内的元素逆序复制到另一个范围中。

copy_backward(vec1.begin(), vec1.end(), vec2.end());

13. 替换相关的算法

replace

将范围内等于某值的元素替换为新值。

replace(vec.begin(), vec.end(), old_value, new_value);

replace_if

将范围内满足条件的元素替换为新值。

replace_if(vec.begin(), vec.end(), [](int x) { return x < 0; }, 0);

replace_copy

将范围内的元素复制到另一个范围,并将等于某值的元素替换为新值。

replace_copy(vec.begin(), vec.end(), result.begin(), old_value, new_value);

replace_copy_if

复制并替换满足条件的元素。

replace_copy_if(vec.begin(), vec.end(), result.begin(), [](int x) { return x < 0; }, 0);

14. 填充与交换相关的算法

fill

用指定值填充范围内的元素。

fill(vec.begin(), vec.end(), value);

fill_n

用指定值填充指定数量的元素。

fill_n(vec.begin(), 5, value);

swap

交换两个变量的值。

swap(a, b);

swap_ranges

交换两个范围的元素。

swap_ranges(vec1.begin(), vec1.end(), vec2.begin());

15. 计算和统计相关的算法

count

统计范围内等于某个值的元素个数。

int cnt = count(vec.begin(), vec.end(), value);

count_if

统计范围内满足条件的元素个数。

int cnt = count_if(vec.begin(), vec.end(), [](int x) { return x % 2 == 0; });

16. 输入输出

printf

printf("%c", "\n "[i < n]);

//相当于:
if(i < n)
	printf(" ");
else
	printf("\n");

这段代码实际上是一种简洁的 C 风格技巧,利用三元运算符来根据条件选择打印的字符:

  • "\n "[i < n]
    "\n " 是一个字符串常量,其中包含了两个字符:\n(换行符)和一个空格字符 ' '
    [i < n] 是条件表达式 i < n 的结果,它是一个布尔值。如果 i < n 为真,则结果是 1;如果为假,则结果是 0。

在 C 语言中,布尔值 true 被视为整数 1,false 被视为整数 0。因此,"\n "[i < n] 这个表达式会返回 "\n " 字符串中的某个字符:
i < ntrue 时,"\n "[1] 会返回空格字符 ' '
i >= n 时,"\n "[0] 会返回换行符 '\n'

  • printf("%c", ...)
    printf("%c", ...) 用于输出一个字符。
    所以,根据条件 i < n 的真假,printf("%c", "\n "[i < n]); 会输出空格字符或换行符。

总结
这段代码会根据 in 的比较结果,决定输出空格还是换行符:
如果 i < n,输出空格 ' '
如果 i >= n,输出换行符 '\n'

sscanf

sscanf(const char *__source, const char *__format, ...):从字符串 __source 里读取变量
比如 sscanf(str,"%d",&a)

  • sscanf 的基本用法
    整型数转换
int year, month, day;
 
int converted = sscanf("20191103", "%04d%02d%02d", &year, &month, &day);
printf("converted=%d, year=%d, month=%d, day=%d/n", converted, year, month, day);

//输出结果:
converted=3, year=2019, month=11, day=03

"%04 d%02 d%02 d"是用来解析字符串的格式,%表示格式转换的开始,d 表示转换为一个整数,04 作为 d 的修饰,表示这是一个长度为 4 位的整数,不足 4 位时以 0 补齐。

例子返回结果等于 3,表示有 3 个数据成功转换,转换成功数目同时取决于被解析的字符串以及其转换格式,如果我们把例子中的格式改为"%04 d%02 d",那么 sscanf 将只返回 2,day 的数值不会被 sscanf 更改。

浮点数转换

double longitude, latitude;
int converted = sscanf("113.123456789 31.123456789", "%lf %lf", &longitude, &latitude);
printf("converted=%d, longitude=%.9lf, latitude=%lf/n", converted, longitude, latitude);

//输出结果:
converted=2, longitude=113.123456789, latitude=31.123457

sscanf 的格式字符串中,f 表示这是一个浮点数,其修饰词 l 表示这是一个 double 的浮点数

  • sscanf 的高级用法

数字+字符串

char str[32] = "";
sscanf("123456abcdedf", "%31[0-9]", str);
printf("str=%s/n", str);

//输出结果:
str=123456

上面的格式中,[0-9] 表示这是一个仅包含 0-9 这几个字符的字符串,前面使用数字 31 修饰词表示这个字符串缓冲区的最大长度 (这也是 sscanf 最为人诟病的地方,很容易出现缓冲区溢出错误,实际上 sscanf 是可以避免出现缓冲区溢出的,只要在书写任何字符串解析的格式时,注意加上其缓冲区尺寸的限制)。

char str[32] = "";
sscanf("123456abcdedf", "%31[0-9a-z]", str);
printf("str=%s/n", str);

//输出结果:
str=123456abcdedf

在格式 [] 中增加了 a-z 的描述。

使用 ^ 示例:

char str[32] = "";
sscanf("123456abcdedf", "%31[^a-z]", str);
printf("str=%s/n", str);

//输出结果:
str=123456

[] 中增加表示相反的意思,上面的 [a-z] 表示一个不包含任何 a-z 之间的字符串

使用 * 示例:

char str[32] = "";
int ret = sscanf("123456abcdedf", "%*[0-9]%31[a-z]", str);
printf("ret=%d, str=%s/n",ret, str);

//输出结果:
ret=1, str=abcdedf

加上 * 修饰表示一个被忽略的数据,同时也不需要为它准备空间存放解析结果。如上面的例子中,我们就只使用了 str 一个参数存放 %31[a-z] 的解析结果,而 sscanf 也只返回 1,表示只解析了一个数据。

sprintf

sprintf(char *__stream, const char *__format, ...):将 __format 字符串里的内容输出到 __stream 中,比如 sprintf(str,"%d",i)

17. 其它常用算法

for_each

对范围内的每个元素应用某个操作。

for_each(vec.begin(), vec.end(), [](int &x) { x += 1; });

transform

对范围内的元素进行变换,并将结果存储到另一个范围中。

transform(vec.begin(), vec.end(), result.begin(), [](int x) { return x * 2; });

clamp

将值限制在某个范围内(C++17 中引入)。

int result = std::clamp(value, min_value, max_value);

inplace_merge

合并两个已排序的相邻子范围,结果仍然有序。

inplace_merge(vec.begin(), middle, vec.end());

auto

  • 如果在一个语句用 auto 声明了多个变量,则初始化这些变量的表达式都必须使对应声明的变量的类型一致
auto intVal=12,doubleVal=0.12;	// 错误

//12是一个int类型的字面值常量,因此intVal判断出来的是int类型,而0.12是一个double类型的字面值常量,因此doubleVal推断出来的是double类型,两者推断的类型不一致,因此这条语句错误

for (auto a:b) 中 b 为一个容器,效果是利用 a 遍历并获得 b 容器中的每一个值,但是 a 无法影响到 b 容器中的元素
for (auto &a:b) 中加了引用符号,可以对容器中的内容进行赋值,即可通过对 a 赋值来做到容器 b 的内容填充

  • for (auto& [x, q] : mp) 是一种基于范围的循环语法,常用于遍历 C++17 引入的结构化绑定语法

[x, q]:这是结构化绑定。map 的元素实际上是一个 pair<const Key, Value>,其中第一个元素是键(key),第二个元素是值(value)。[x, q]pair 的键和值分别绑定到 xq 上,允许我们更方便地访问键值对

fixed 和 setprecision

cout << fixed << setprecision(5) << x << endl;

__builtin

位运算函数

//无符号整型(unsigned int / unsigned long / unsigned long long)
__builtin_ctz( ) / __buitlin_ctzl( ) / __buitlin_ctzll( )	// 返回括号内数的二进制表示数末尾0的个数
__buitlin_clz( ) / __buitlin_clzl( ) / __buitlin_clzll( )	// 返回括号内数的二进制表示数前导0的个数

__builtin_popcount( ) / __builtin_popcountl( ) / __builtin_popcountll( )// 返回括号内数的二进制表示数1的个数
__builtin_parity( ) / __builtin_parityl( ) / __builtin_parityll( )// 判断括号中数的二进制表示数1的个数的奇偶性(偶数返回0 , 奇数返回1)

//有符号整型(int / long / long long)
__builtin_ffs( ) / __builtin_ffsl( ) / __builtin_ffsll( )	// 返回括号中数的二进制表示数的最后一个1在第几位(从后往前算)


//其他
__builtin_sqrt( ) / __builtin_sqrtf( ) 	//快速开平方
posted @ 2025-07-28 09:52  Aurora_333  阅读(16)  评论(0)    收藏  举报