是个好东西
cpp
ios::sync_with_stdio(false);
cin.tie(0);
超级头文件 bits万能头无法在部分编译器使用
cpp
// C++ 基础与 I/O
#include <iostream>
#include <cstdio>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <cstring>
// 容器与数据结构
#include <vector>
#include <list>
#include <deque>
#include <stack>
#include <queue>
#include <map>
#include <unordered_map>
#include <set>
#include <unordered_set>
#include <bitset>
#include <array>
// 算法与数学函数
#include <algorithm>
#include <cmath>
#include <numeric>
#include <functional>
#include <utility>
#include <iterator>
// 字符串处理
#include <string>
#include <cctype>
// 时间与随机数
#include <ctime>
#include <chrono>
#include <random>
// 智能指针与内存管理
#include <memory>
初始化
cpp
long long num = 0, money = 0;
初始化参数列表
cpp
Node(int x1, int y1, int x2, int y2, int l)
: x1(x1), y1(y1), x2(x2), y2(y2), lay(l) {}
我chovy 你是不是用迭代器不打括号??!给我打好了啊!
cpp
if (px >= (*it).x1 && px <= v.x2 && py <= v.y2)
你是不是先擦除再使用了?!一定要先拷贝一份再用
cpp
vec.insert(vec.begin(), (*it));//放到最前端
vec.erase(it);
你是傻逼吗?在用it的迭代中删了it???

不要在迭代器的for中进行删除,要用下标进行访问,记录下标,break出循环后再erase,这样可以避免逆序迭代无法删除的问题。

所有的push_backTMD是从后扔进去!
你有没有手贱把j循环的自增打成i?!
你TM调试信息是不是忘记注释了?!
你有没有把一些变量写在循环里了?!

你有没有多次输入的时候清空变量?!

你TM在贪心/DP中写break了吗?不然怎么跳出去?

输出小数的除法你加(double)A/B了吗?

for输出后你换行了吗?!
取余不变性

前置空格法
cpp
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
if(j > 0) cout << " "; // 防坑:非首元素前加空格
cout << A[i][j];
}
cout << endl;
}
struct要加分号!!!
注意二维数组坐标问题
注意二维数组题目的初始坐标问题!!!这里是正整数uv,因此在遍历图的时候是从1开始的,那么前置空格法也是要求if(j>1) cout<<" ";
https://www.luogu.com.cn/problem/T419391

cpp
#include<iostream>
using namespace std;
int A[1005][1005],n,m;
int main(){
cin>>n>>m;
int n1,n2;
for(int i=0;i<m;i++){
cin>>n1>>n2;
A[n1][n2]=1;
A[n2][n1]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(j!=1) cout<<" ";
cout<<A[i][j];
}
cout<<endl;
}
for(int i=1;i<=n;i++){
int d=0;
for(int j=1;j<=n;j++){
if(A[i][j]) d++;
}
cout<<d;
for(int j=1;j<=n;j++){
if(A[i][j]) cout<<" "<<j;
}
cout<<endl;
}
}
特殊输入方式

这道题是一次输入一行,但是要转为二维数组。
可以用char temp定义临时字符,然后每次输入都只接受一个字节的数据,然后就可以用temp-'0'转换了。
cpp
cin>>n>>m;
char temp;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>temp;
maze[i][j]=temp-'0';
}
}

cpp
char temp;
for(int i=1;i<=N;i++){
for(int j=1;j<=M;j++){
cin>>temp;
if(temp=='#'){
maze[i][j]=0;
}else{
maze[i][j]=1;
}
}
}
带有空格的整行输入要用getline(cin,s)否则会截断
如果题目给定的后缀表达式非常长,或者中间带有空格换行,cin >> s 遇到空格就会停止读取,导致后面的数字和运算符全部丢失。必须使用 getline(cin, s)。
混合数组和字符的输入处理

cpp
string s;
getline(cin, s);
stack<long long> st;
string num = "";
for (char c : s) {
//第一个可能是数字也可能是负号,要完整接受一个多位数字:先通过一次性收集齐"."前的字符,然后一次stod变成数字
if (isdigit(c)) {
num += c;
}
特殊的输出方式
要求带精度小数的输出

cpp
cout<<fixed<<setprecision(2)<<curv<<endl;
注意 拓扑问题一个节点可以有多个输出边,因此要一直输入
cpp
for(int i=1;i<=N;i++){
//注意 拓扑问题一个节点可以有多个输出边,因此要一直输入
while(cin>>v && v){
cin>>v;
addEdge(i,v);
inE[v]++;
}
}

排序
桶排序 T407375
https://www.luogu.com.cn/problem/T407375
用vector开桶,计算桶的大小,桶大小=max-min+1/N,
然后输入到各个桶,idx=(ai-min)/N; if(idx==N) idx=N-1; pushback(ai)

cpp
#include<bits/stdc++.h>
using namespace std;
int N;
int a[100005];
//开N个桶,涉及范围max min,单个桶的大小就是range=max-min+1 / N
int main(){
vector<int> buk[100005];
cin>>N;
int min_n=(int)1e9, max_n=0;
for(int i=0;i<N;i++){
cin>>a[i];
max_n=max(max_n,a[i]);
min_n=min(min_n,a[i]);
}
long long range = (long long)max_n - min_n + 1;
double bk_size = (double)range / N;
for(int i=0;i<N;i++){
int idx=(a[i]-min_n)/bk_size;//落入区间是当前值-最小值/桶大小
if(idx >= N) idx = N - 1;
buk[idx].push_back(a[i]);
}
for(int i=0;i<N;i++){
//对各个桶排
sort(buk[i].begin(),buk[i].end());
for(int x:buk[i]){
cout<<x<<" ";
}
}
}
链表

创建链表
可以用Node *head在堆上创建,这种需要dlete删除。
也可以用Node head;
head.val=10;
head->next=&node1;(指针必须用->访问)


遍历链表
初始节点指向head->next(第一个有值),然后通过cur->val遍历访问。

插入节点(p后面插一个)
new新节点,填值,当前下一个=原先下一个,原先下一个=当前

删除节点(p的下一个)
暂存当前节点,指向下一个的下一个,删除d

查找节点

模板
cpp
#include<iostream>
using namespace std;
struct Node{
int val;
Node* next;
};
void insert(Node* p,int val){
Node*q = new Node();
q->val = val;
q->next = p->next;
p->next = q;
}
Node* findf(Node* head, int target){
Node* cur = head->next;
while(cur!=NULL && cur->val!=target){
cur=cur->next;
}
return cur;
}
void printn(Node* head){
Node* cur = head->next;
while(cur!=NULL){
cout<<cur->val<<" ";
cur=cur->next;
}
}
void deletep(Node* head,int tar){
Node* cur = head;
while(cur!=NULL && cur->next!=NULL){
if(cur->next->val==tar){
Node* d = cur->next;
cur->next=cur->next->next;
delete d;
}else{
cur=cur->next;
}
}
}
int main(){
Node* head=new Node();
Node* n1 = new Node();
n1->val=5;
head->next=n1;
Node* n2 = new Node();
n2->val=10;
n1->next=n2;
Node* n3 = new Node();
n2->next=n3;
n3->val=15;
insert(head,10);
insert(head,23);
printn(head);
cout<<endl;
Node* tar = findf(head,5);
insert(tar,11);
deletep(head,10);
printn(head);
cout<<endl;
return 0;
}
静态链表(数组模拟)
创建链表


遍历链表

插入链表

删除链表

查找链表

模板
cpp
/*每个结点存一个int型数据
创建一个头结点不存数据的空链表
创建三个结点,数值分别为5、10、15
5->10->15
在链表头部插入数值为10的结点
在链表头部插入数值为23的结点
依次输出当前链表的值,用空格隔开,输出所有值后换行
找到链表值为5的结点,在其后插入一个值为11的结点
删除所有值为10的结点
依次输出当前链表的值,用空格隔开,输出所有值后换行*/
#include<iostream>
using namespace std;
struct Node {
int val;
int next;
};
Node nodes[1000];
int cnt = 1;
void insert(int pre, int val) {//在pre后节点加一个
nodes[cnt].val = val;
nodes[cnt].next = nodes[pre].next;
nodes[pre].next = cnt;
cnt++;
}
void printn(int head) {
int cur = nodes[head].next;
while (cur) {
cout << nodes[cur].val << " ";
cur = nodes[cur].next;
}
cout << endl;
}
int findn(int head, int tar) {
int cur = head;
while (nodes[cur].next != 0 && nodes[nodes[cur].next].val != tar) {
cur = nodes[cur].next;//一定不是自然递增,索引不连续
}
return nodes[cur].next;//返回找到的本届点
}
void deleteAll(int head, int tar) {
int cur = head;
while (nodes[cur].next != 0) {//其实不用判定当前是否为零,因为从head开始的地址就不为0
if (nodes[nodes[cur].next].val == tar) {
nodes[cur].next = nodes[nodes[cur].next].next;
}
else {
cur = nodes[cur].next;
}
}
}
int main() {
int head = 0;
insert(head, 15);
insert(head, 10);
insert(head, 5);
insert(head, 10);
insert(head, 23);
printn(head);
int f = findn(head, 5);
insert(f, 11);
deleteAll(head, 10);
printn(head);
return 0;
}
约瑟夫问题 P1996
https://www.luogu.com.cn/problem/P1996

cpp
#include<bits/stdc++.h>
using namespace std;
//维护一个下标 一个当前计数值 下标用于删除和输出 当前计数值用于判断m 一个剩余数量
int cur=1,cnt=0,rem;
int pp[1005];
int main(){
int n,m;
cin>>n>>m;
//cur是下标 cnt是计数值 每次cur==m都会触发清零 cnt==0;
//当剩余数量
rem=n;
while(rem){
if(pp[cur]==0){//如果当前人活着
cnt++;//计数++
if(cnt==m){//如果这个人正好是cnt第m个就杀了
cout<<cur<<" ";
cnt=0;//清零计数
pp[cur]=1;//标记死人
rem--;//剩余人数--
}
}
cur++;
if(cur>n) cur=1;
}
}

队列
入队
头往后

出队
尾巴往前移

环形队列

STL队列 queue

STL双端队列 deque

栈

STL栈

前中后缀表达式

中缀转前缀

中缀转后缀

前后缀表达式的计算
前缀表达式的计算
前缀从右往左压入栈,遇到运算符弹栈计算。

后缀表达式的计算
后缀从左往右压入栈,遇到运算发弹栈计算。注意顺序是次栈顶<运算>栈顶。

练习 P1449
https://www.luogu.com.cn/problem/P1449

cpp
#include <iostream>
#include <stack>
#include <string>
using namespace std;
//
int main() {
string s;
getline(cin, s);
stack<long long> st;
string num = "";
for (char c : s) {
//第一个可能是数字也可能是负号,要完整接受一个多位数字:先通过一次性收集齐"."前的字符,然后一次stod变成数字
if (isdigit(c)) {
num += c;
}
else if (c == '.') {//如果遇到是.就std
st.push(stoll(num));
num.clear();//输入完要清空
}
else if (c == '@') {
break;
}
else {
long long op2 = st.top(); st.pop();
long long op1 = st.top(); st.pop();
if (c == '+') {
//cout << op1 <<"+"<< op2 <<"="<<op1+op2 << endl;
st.push(op1 + op2);
}
else if (c == '-') {
//cout << op1 << "-" << op2 << "=" << op1 - op2 << endl;
st.push(op1 - op2);
}
else if (c == '*') {
//cout << op1 << "*" << op2 << "=" << op1 * op2 << endl;
st.push(op1 * op2);
}
else {
//cout << op1 << "/" << op2 << "=" << op1 / op2 << endl;
st.push(op1 / op2);
}
}
}
cout << st.top() << endl;
}
STL
排序sort()
正序
cpp
sort(num.begin(), num.end());
逆序
cpp
// rbegin() 指向最后一个元素,rend() 指向第一个元素的前一个位置
sort(num.rbegin(), num.rend());
自定义类型使用sort()

如果 cmp(a, b) 返回 true,sort 就认为 a 应该排在 b 的前面。
如果 cmp(a, b) 返回 false,sort 就认为 a 应该排在 b 的后面(或者顺序不变)。
第二关键词排序
cpp
// 比较函数:优先按时间升序;时间相同时,按原始编号(idx)升序
bool cmp(const Node& a, const Node& b) {
if (a.t != b.t) return a.t < b.t;
return a.idx < b.idx;
}

精度限制输出
cpp
cout << fixed << setprecision(10) << ans << endl;
清空/初始化 Memset
对象、多少、大小sizeof
cpp
memset(arr, 0, sizeof(arr)); // 将整个数组的每个字节设为0
容器

顺序容器:通过位置来访存元素
容器适配器:集成了基本容器的容器
关联容器:字典,用于表达键值对关系
无序容器:哈希表
Vector
vector初始化长度后,填充不可以用push_back(),否则只是往后加,必须用veci填充。
如果没有初始化长度才可以用push_back().
vector开在main外面。

二维vector
矩形二维vector

可变长一维数组的拼接 与 创建 遍历
这里例子每一行是i的大小递增。
如果要求任意长度,可以用resize(),再push_back进vec4中。

对Vector的遍历
一种是
Resize 操作 例题
带初始值的resize,将行数扩到4,第四行是全0的大小为4的vector
cpp
std::vector<std::vector<int>> matrix(3, std::vector<int>(3, 0)); // 初始为 3x3
// 将行数扩容到 4,新增的第4行是一个包含 4 个元素且全为 0 的 vector
matrix.resize(4, std::vector<int>(4, 0));
不带初始值的resize,将行数扩到4,第四行是null,在使用的时候必须对列resize。
cpp
std::vector<std::vector<int>> matrix(3, std::vector<int>(3, 0)); // 初始为 3x3
// ❌ 危险操作:行数变成了 4,但第 4 行(matrix[3])里面没有分配任何空间!
matrix.resize(4);
P3613
https://www.luogu.com.cn/problem/P3613

cpp
#include<iostream>
#include<vector>
using namespace std;
vector<vector<int>> store;
int main(){
int n,q;
cin>>n>>q;
store.resize(n+1);
int p,i,j,k;
for(int v=0;v<q;v++){
cin>>p;
if(p==1){
cin>>i>>j>>k;
if(store[i].size()<=j){store[i].resize(j+1);}
store[i][j]=k;
}else{
cin>>i>>j;
cout<<store[i][j]<<endl;
}
}
return 0;
}
List

List的遍历
实际上直接for(auto x:lst)即可。

List的查找
找第一个/找所有的val都可以

可以直接用algorithm的函数
find(begin,end,val)

List的删除
删除单个节点
erase(find(10))
删除所有节点
remove(10)
List的插入
在后面插入 push_back
在前面插入 push_front
在i之前插入
L.insert(i,100)
在i之后插入
auto i=find(5)
i++
L.insert(i,1000)

翻转链表
List与Vector的转换
1. Vector转List
cpp
std::vector<int> myVec = {1, 2, 3, 4};
// 利用迭代器范围直接构造 list
std::list<int> myList(myVec.begin(), myVec.end());
2. List转Vector
这是最直接的方法。std::vector 提供了一个接受迭代器范围的构造函数,可以直接接收 std::list 的头尾迭代器进行初始化:
cpp
std::list<int> myList = {5, 6, 7, 8};
// 利用迭代器范围直接构造 vector
std::vector<int> myVec(myList.begin(), myList.end());
应用
快排回去
List排序很慢
cpp
std::list<int> myList = {...}; // 假设包含大量无序数据
// 1. 将 list 转换为 vector
std::vector<int> tempVec(myList.begin(), myList.end());
// 2. 对 vector 进行高效排序
std::sort(tempVec.begin(), tempVec.end());
// 3. 将排好序的数据拷贝回 list
10myList.assign(tempVec.begin(), tempVec.end());
Map 字典(增删改查 且有不重复唯一键 带值去重)
不支持随机访问,必须用it迭代器

会对key建立索引。
如果是自定义类型的key,必须实现<
基本用法


find返回的是迭代器,如果是尾后迭代器就是没找到。
遍历的时候需要用i->first ->second输出元素。
练习
T424396
https://www.luogu.com.cn/problem/T424596

P1918
https://www.luogu.com.cn/problem/P1918
Set(增删改查 且本身就是可作为索引 唯一 去重)
不支持随机访问 必须用it迭代。

注意,set是有序的,是可以建立索引和遍历的。集合是无序的。
注意,set的插入是insert。

set的遍历
输出是有序的 这里插入10 15 11但是输出是唯一且有序的 7 8 10 11 15.

练习
带重复的第k小整数,但是要求不重复的顺序。最核心的痛点就是去重。

注意,set不支持随机访问,必须顺序遍历
cpp
//迭代器不可以跳变,只能一个个叠加上去。
auto it=s.begin();
for(int i=0;i<k-1;i++){
it++;
}
cpp
#include<bits/stdc++.h>
using namespace std;
set<int> s;
int main(){
int n,k;
cin>>n>>k;
for(int i=0;i<n;i++){
int temp;
cin>>temp;
s.insert(temp);
}
if(k > s.size()){
cout << "NO RESULT" << endl;
return 0;
}
//迭代器不可以跳变,只能一个个叠加上去。
auto it=s.begin();
for(int i=0;i<k-1;i++){
it++;
}
cout<<*it<<endl;
}
set和Map的upper_bound和lower_bound
当查找基于红黑树实现的容器,比如set和map,都可以用bound函数找到第一个满足大于这个值/小于这个值的值。
P5250
https://www.luogu.com.cn/problem/P5250

正确解法:利用 lower_bound
std::set 内部是红黑树,支持 O(logN)的查找。我们可以利用 s.lower_bound(val):
- 它会返回第一个 大于等于
val的元素的迭代器。
策略:
- 精确查找 :先
find(val),如果有直接取走。 - 模糊查找 :
- 用
lower_bound(val)找到第一个≥val 的数,记为it_high(候选1:偏大的)。 - 如果
it_high不是begin(),那么it_high的前一个数prev(it_high)一定是 <val 的最大数,记为it_low(候选2:偏小的)。 - 比较这两个候选者与
val的差值。题目要求"一样接近取较短",意味着如果差值相等,优先取it_low。
- 用
找第一个大于等于val的迭代器
cpp
auto it_high=s.lower_bound(val)
然后就可以用
cpp
*(--it_high)
表达后一个
cpp
*(++it_high)
cpp
#include<bits/stdc++.h>
using namespace std;
set<int> s;
//vector<int> vec;
int main(){
int m;
cin>>m;
for(int i=0;i<m;i++){
int op=0;
cin>>op;
if(op==1){
int val=0;
cin>>val;
auto it=s.find(val);
if(it==s.end()){//如果没招到
s.insert(val);
}else{
cout<<"Already Exist"<<endl;
}
}else if(op==2){
int val=0;
cin>>val;
auto it=s.find(val);
if(s.empty()){
cout<<"Empty"<<endl;
}else if(it!=s.end()){
s.erase(val);
cout<<val<<endl;
}else{//如果找不到长度一样的,就找最小的 直接上二分
auto it_high=s.lower_bound(val);//去找第一个满足大于val的迭代器
int chose=0;
if(it_high==s.end()){//如果所有的都不满足,说明都太短了
chose=*(--it_high);
}else if(it_high==s.begin()){//所有的都大于,都太长了
chose=*it_high;
}else{
int v_high=*it_high;
int v_low=*(--it_high);
if(v_high-val<val-v_low){//更近就选更近的,一样就选短的
chose=v_high;
}else{
chose=v_low;
}
}
s.erase(chose);
cout<<chose<<endl;
}
}
}
}
哈希(超大范围的不重复统计)不咋考
一般都需要offset偏移量

需要将一个大范围压缩到一个小范围时,比如key1的值是-1亿,key4是一亿,就需要映射到小桶中。因为数组不能开1e7的桶。

| 特性 | std::map |
unordered_map (Hash) |
|---|---|---|
| 底层结构 | 红黑树 (平衡二叉树) | 哈希表 (数组+链表) |
| 查找/插入速度 | logN | 1 |
| 数据顺序 | 自动排序 (从小到大) | 无序 (乱序) |
| 内存占用 | 较小 (每个节点只需存左右孩子指针) | 很大 (需要预留大量空桶以防冲突) |
| 支持有序访问 | 是 | 否 |
Unordered_Map和Unordered_Set



