Acwing笔记汇总

一、《语法基础课》

Acwing《语法基础课》笔记

万能头文件: bits/stdc++.h

字符的读入

  1. scanf("%c%c", &a, &b); // 会把空格读入
  2. cin >> a >> b; // 会忽略中间的空格(1个或多个)

max数学表达式:max = (a + b + |a − b|) / 2

swap函数:
在新c++编译器中,iostream含有swap函数,可交换基础类型,如swap(int x, int y),而不必swap(int &x, int &y)
旧一点的版本需要导入#include
#include 里有swap(x, y)函数

逗号运算符:C++的逗号运算符对逗号前后的表达式进行运算,然后舍弃前一个表达式的返回值,仅仅返回最后一个表达式的返回值

  1. if (表达式1, 表达式2, 表达式3) {...}
  2. 等价于
  3. 表达式1;
  4. 表达式2;
  5. if (表达式3) {...}
  6. if(cin >> x && x > 0) {...}
  7. if(cin >> x, x > 0) {...} // "cin >> x"仅做执行,然后抛弃其返回值

浮点数比较问题:C++中,表达式Acwing笔记---《语法基础课》 - 图1并不成立。由于浮点数精度损失,应该用Acwing笔记---《语法基础课》 - 图2去判断,eps一般取10−6。

cstring一些函数用法
memset赋值是按字节赋值,因此只有赋予-1和0时才与预期一致。其最后一个参数的单位是Byte
memset(arr, 0, n sizeof(int))
memset(arr, -1, n
sizeof(int))

sizeof 可不加括号,即可这样使用sizeof a,其返回单位是Byte
memcpy用于拷贝数组,格式为memcpy(dest,src,sizeof(src))

数组初始化的坑:在函数内定义的数组不会自动初始化为0,都是随机数,例如main函数里。而在函数外定义的数组会自动初始化为0。

读取字符串的方法

  1. char s[N];
  2. scanf("%s", s); // 不能读取含空格、换行符的字符串
  3. gets(s); // 能读取含空格的字符串,同时自动去掉换行符\n
  4. fgets(s, N, stdin); // 能读取含空格的字符串,但不会去掉换行符\n。【注意】
  1. #include <string>
  2. string str;
  3. cin >> str; // 不能读取含空格、换行符的字符串
  4. getline(cin, str); // 能读取含空格的字符串,同时自动去掉换行符\n

字符串操作

  1. #include <cstring> // 或<string.h>
  2. char a[N], b[N];
  3. strlen(a); // O(N)复杂度,使用前最好用变量保存字符串长度。统计的长度不包括`\0`
  4. strcat(a, b); // 把字符串b拼接到a之后,拼接后的字符串保存在a中
  5. strcmp(a, b); // 根据字典排序比较字符串
  6. strcpy(b, a); // 把字符串a的内容拷贝到字符串b
  7. for (int i = 0; str[i]; i++) {...} // 遍历字符串
  1. string str;
  2. string s(5, 'a'); // 构造重复字符的字符串
  3. str.empty(); // 判空
  4. str.size(); // 长度,与stelen()不同的是,这个复杂度是O(1),不用额外的变量保存
  5. str.c_str(); // 转成char数组,此时才可用printf输出
  6. str.substr(begin, length); // 子串
  7. str.pop_back(); // 删除最后一个字符
  8. // 字符串比较">"、"<"
  9. // 字符串拼接"+"
  10. //注意:使用+对字符串拼接时,要求左右两边至少有一个string对象,即str = "a" + "b";会报错。
  11. for (char ch : str) {...} // 遍历(不可修改字符)
  12. for (char &ch : str) {...} // 遍历(可修改字符)

字符串流:

  1. #include <sstream>
  2. string s;
  3. stringstream ssin(s);
  4. while(ssin >> s) {...} // 按空格拆分s,例如英语句子拆分单词
  5. // 可用如下代码代替
  6. while(cin >> word) {
  7. ...
  8. }

char数组难点

  1. char a[] = {'C', '+', '+'};
  2. char b[4] = {'D', '+', '+', '\0'};
  3. char c[5] = {'E', '+', '+', '\0'}; // 最后一个位置会补\0
  4. cout << a << endl; // 输出"C++D++",因为字符数组a不会自动添加'\0',cout会读取到b的部分

函数中的数组参数

  1. int size(int a[]) {
  2. return sizeof a;
  3. }
  4. int main() {
  5. int a[10];
  6. cout << sizeof a << endl; // 4B * 10 = 40B
  7. cout << size(a) << endl;
  8. // 8B,虽然函数f能修改实参a的内容,但其本质是一个不同的数组指针指向数组的内存空间.
  9. //故对函数内的数组参数a调用sizeof,返回的是数组指针的长度。
  10. //在64位系统中,指针的长度等于64b=8B
  11. }

默认参数值

  1. void f(int a, int b = 10) {...} // 需要默认值的变量只能放在靠后的位置

内联

  1. inline void f() {...} // 编译时把函数体复制到调用函数的位置,减少函数跳转次数

fgets的坑

  1. fgets会读入\n,因此遍历字符串时,
  2. 应当用 for (int i = 0; str[i] != '\n'; i++),
  3. 而不能用 for (int i = 0; str[i]; i++)

结构体

  1. // 定义
  2. struct Node {
  3. int val;
  4. Node* next;
  5. // 构造器
  6. Node(int _val): val(_val), next(NULL) {} // 编译更快
  7. Node(int _val) {
  8. val = _val;
  9. }
  10. };
  11. // 使用
  12. Node* p = new Node(1);
  13. //注意:链表中的头结点指的是链表第一个结点的地址,而不是结点本身。

  1. class Node {
  2. private:
  3. ...
  4. public:
  5. ...
  6. }; // 注意类末尾要加分号!

类与结构体的区别:结构体默认是public,类默认是private。

数组类容器:

  1. #include <vector>
  2. // 定义
  3. vector<int> a; // 一维数组
  4. vector<int> b[N]; // 二维数组
  5. // 初始化
  6. vector<int> a({1, 2, 3});
  7. // 操作
  8. a[k]; // 取值
  9. a.size(); // 长度
  10. a.empty(); // 判空
  11. a.clear(); // 清空
  12. a.front(); // 读取第1个元素
  13. a.back(); // 读取最后1个元素
  14. a.push_back(x); // 在末尾插入元素
  15. int x = a.pop_back(); // 删除末尾元素并返回
  16. int* p = lower_bound(a, a + a.size(), x);
  17. // 查找数组在指定范围内大于等于x的元素地址(要求数组有序)
  18. int* p = upper_bound(a, a + a.size(), x);
  19. // 查找数组在指定范围内大于x的元素地址(要求数组有序)
  20. int index = lower_bound(a, a + a.size(), x); - a;
  21. // 查找数组在指定范围内大于等于x的元素下标(要求数组有序)
  22. int index = upper_bound(a, a + a.size(), x); - a;
  23. // 查找数组在指定范围内大于x的元素下标(要求数组有序)
  24. // 遍历
  25. for (int i = 0; i < a.size(); i++) {...}
  26. // 方式1,通过a[i]读取元素值
  27. for (vector<int>::iterator i = a.begin(); i < a.end(); i++) {...}
  28. // 方式2(迭代器),通过*i读取元素值
  29. for (auto i = a.begin(); i < a.end(); i++) {...}
  30. // 方式3(迭代器简化版)
  31. for (int x : a) {...}
  32. // 方式4,通过x读取元素值
  33. 说明:
  34. vector是变长数组,类似javaArrayList
  35. a.begin()返回的是vector1个元素的地址,而a.end()返回的是最后一个元素的下一个位置的地址
  36. a.end() - a.begin() == a.size()
  37. *a.begin() == a[0]
  1. #include <queue>
  2. /**************************************************
  3. ** 普通队列queue
  4. ***************************************************/
  5. // 定义
  6. queue<int> q;
  7. // 操作
  8. q.push(x); // 入队(末尾插入元素)
  9. int x = q.pop(); // 出队(删除第1个元素)
  10. a.front(); // 查看队头元素
  11. a.back(); // 查看队尾元素
  12. // a.clear()
  13. /**************************************************
  14. ** 优先队列(堆)
  15. ***************************************************/
  16. // 元素为基本类型
  17. priority_queue<int> a; // 大根堆
  18. priority_queue<int, vector<int>, less<int>> a; // 大根堆
  19. priority_queue<int, vector<int>, greater<int>> b; // 小根堆
  20. // 元素为自定义类型
  21. struct Rec {
  22. int a, b;
  23. // 大根堆需要自定义类重载<号
  24. bool operator< (const Rec& t) const {
  25. return a < t.a;
  26. }
  27. // 小根堆需要自定义类重载>号
  28. bool operator> (const Rec& t) const {
  29. return a > t.a;
  30. }
  31. }
  32. priority_queue<Rec> a; // 大根堆
  33. priority_queue<Rec, vector<Rec>, less<Rec>> a; // 大根堆
  34. priority_queue<Rec, vector<Rec>, greater<Rec>> b; // 小根堆
  35. // 操作
  36. a.push(x); // 插入元素(位置不确定)
  37. a.top(); // 查看堆顶元素(大根堆是最大值,小根堆是最小值)
  38. a.pop(); // 删除堆顶元素(大根堆是最大值,小根堆是最小值)
  39. 注意:
  40. 队列没有clear()方法
  41. 优先队列插入时无序,输出时有序
  42. 优先队列存储自定义类型时,需要重载运算符
  43. 大根堆重载<
  44. 小根堆重载>
  1. #include <stack>
  2. // 定义
  3. stack<int> s;
  4. // 操作
  5. s.push(x); // 入栈
  6. s.top(); // 查看栈顶
  7. s.pop(); // 出栈(不放回出栈元素!)
  8. //注意:stack的pop()方法不像java返回栈顶元素!
  1. #include <deque>
  2. // 定义
  3. deque<int> q;
  4. // 操作
  5. q[i] // 随机访问
  6. q.begin(); // 队头元素地址,用*q.begin()读取元素
  7. q.end(); // 队尾元素地址,用*q.end()读取元素
  8. q.front(); // 队头元素值
  9. q.back(); // 队尾元素值
  10. push_back(); // 队尾插入元素
  11. push_front(); // 队头插入元素
  12. pop_back(); // 队尾删除元素
  13. pop_front(); // 队头插入元素
  1. #include <set>
  2. // 定义
  3. set<int> s; // 集合
  4. multiset<int> ms; // 多重集合(允许元素重复)
  5. // 操作
  6. s.size();
  7. s.empty();
  8. s.claer();
  9. s.begin();
  10. s.end();
  11. s.insert(x);
  12. s.find(x); // 返回迭代器,可用if(s.find(x) == s.end())判断是否存在元素x
  13. s.lower_bound(x); // 返回大于等于x的最小元素的迭代器
  14. s.upper_bound(x); // 返回大于x的最小元素的迭代器
  15. s.erase(x); // 删除x并返回迭代器
  16. s.count(x); // 统计x出现的次数(普通集合只会返回0或1,多重集合可能返回大于1的数)
  17. 说明:
  18. 自定义类要求重载<
  19. find()、erase()、lower_bound()和upper_bound()都是O(logn)复杂度
  20. count()是O(k+logn)复杂度
  1. #include <unordered_set>
  2. unordered_set<int> s; // 哈希表
  3. unordered_multiset<int> s;
  1. #include <bitset>
  2. // 定义二进制串
  3. bitset s;
  4. // 操作
  5. s.count(); // 1的个数
  6. s.set(p); // 第p位设为1
  7. s.reset(p); // 第p位设为0
  8. 说明:
  9. bitset元素支持位运算符&、|和~等等
  10. x的第k位二进制数:x >> k & 1
  11. x从右起的第11lowbit(x) = x & -x,实际上是原码和补码做与操作。
  12. 例如110110返回1011000返回1000

有序对容器

  1. pair:
  2. pair<int, int> a = {5, 6};
  3. pair<int, int> b = make_pair(5, 6);
  4. map
  5. #include <map>
  6. map<string, int> m; // 定义
  7. // 操作
  8. a["a"] = 4; // 类似数组的操作
  9. m.insert();
  10. m.find();
  11. unordered_map
  12. 哈希映射,效率更高

algorithm算法库

  1. #include <algorithm>
  2. vector<int> a;
  3. // 翻转
  4. reverse(a.begin(), a.end());
  5. reverse(a, a + a.size());
  6. // 去重
  7. unique(a, a + a.size()); // 返回去重后最后一个元素的地址
  8. int m = unique(a, a + a.size()) - a; // 去重后数组的长度
  9. a.erase(unique(a.begin(), a.end()), a.end()); // 真删除重复元素
  10. // 打乱
  11. random_shuffle(a.begin(), a.end());
  12. // 排序
  13. sort(a.begin(), a.end()); // 升序
  14. sort(a.begin(), a.end(), greater<int>()); // 降序
  15. bool cmp(int a, int b) {return a - b;} // 自定义比较方法
  16. sort(a.begin(), a.end(), cmp); // 自定义排序
  17. 注意:
  18. random_shuffle()常结合ctimesrand( time(0) )使用
  19. unique并没有真的删除重复元素,它仅将重复的元素放到非重复元素部分的后边