动态规划
1.组合数是指不强调顺序(即123与321是同一个集合,只算一次),排列数强调顺序(即即123与321是不同的集合,算两次)。
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品
2.01背包是先遍历物品再遍历重量(滚动数组是需要重量倒序遍历),完全背包需要(滚动数组是需要重量正序遍历)),求解方法类,多少种类(需要参考3-4条的情况)。
3.动态规划一般为最大最小类问题(入手点为递归式子)
string知识点
1. st1.find('a',1);后面的数字代表从什么位置开始查找 (无迭代器)
int post=b.find(a); cout<<post<<endl;
2 string s2 = s1.erase(0, 2); string& erase(int pos=0, int n=npos);
s.erase(s.begin()+a-1,s.begin()+b); (左闭右开)
erase
函数使用迭代器来指定要删除的范围而std::string::begin()
返回一个指向字符串第一个字符的迭代器。通过将偏移量(如a - 1
和b
)与begin()
返回的迭代器相结合,可以得到正确的迭代器位置,从而指定要删除的字符范围
erase的作用是从位置 pos 开始 , 删除长度为 n 的子字符串 , 如果 n 的值超过了字符串的长度 , 那么整个字符串都会被删除 ;
3 string a=s1.insert(2,s2); string &insert(int pos, const string &s);
insert作用是在字符串的指定位置 pos 插入另一个字符串 s , 字符串类型可以是 char* 类型 , 也可以是 string 类型 (用法类似于substr)
4 string z=s.substr(5,3); //获得字符串s中从第5位开始的长度为3的字符串
string substr(int pos = 0, int n = npos) const;
substr
函数使用基于位置的无符号整数作为参数,直接指定起始位置和长度,不需要通过迭代器来操作
substr会截取字符串的一部分 , 并返回一个新字符串 ; 截取的起始位置是 pos , 截取的字符数量是 n ; 如果 n 的值超过了字符串的长度 ; 那么整个字符串都会被截取 ;
5.string的replace()用法 (replace的用法同erase一样)
words.replace(words.begin()+pos,words.begin()+pos+a[i].size(),l);
第一个是对应的初始位置,第二个是结束,最后想要替换的string数据
6.涉及string的数据遍历情况时候一定要str.size()-1
7.stoi函数是C++标准库中的一个函数,用于将字符串转换成整数
str
:待转换的字符串(支持含前导空格和正负号)idx
(可选):输出参数,记录起始字符位置索引base
(可选):指定进制(2~36,0表示自动推断)
int stoi(const string& str, size_t* idx = 0, int base = 10);
stoi(str, 0, 2); //将字符串 str 从 0 位置开始到末尾的 2 进制转换为十进制
queue的知识
q.push(x);//从队尾加入一个数x(queue的知识)
q.pop();//弹出队首元素
q.front();//返回队首元素
q.size();//返回队列长度
q.empty();//如果队列为空,返回true,否则返回false
scanf知识点
1"%d %d" 显式空格:匹配输入中的任意数量连续空白字符(空格、Tab、换行等) "12\n34" → 读取12和34
"%d%d" 隐式处理:%d自动跳过前导空白字符,但两个%d之间不允许有其他非空白字符 "12\t34" → 读取12和34(两种方式都是正确的,但一般使用第二种)
图论知识点
1.图的存储方式:邻接矩阵(即二维数组),邻接表(数组加链表)
2.上下左右遍历方式 第一种方法
dir[4][2] = {0, 1, 1, 0, -1, 0, 0, -1};// 表示四个方向
for (int i = 0; i < 4; i++) { // 开始想当前节点的四个方向左右上下去遍历
int nextx = curx + dir[i][0];
int nexty = cury + dir[i][1]; // 获取周边四个方向的坐标
}
第二种方法
int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
for(int i=0;i<4;i++)
{
dfs(x+dx[i],y+dy[i]);
}
3.在BFS中,必须在节点入队时立即标记,避免重复访问(visited
设置为true
)
4.dijkstra算法(模板)(处理单源最短路径)
int grid[550][550]; // 存放所有的边,例如 edges[i][j] 代表从i到j的距离
int dist[550]; // 记录当前所有点到起点的距离
int vis[550]; // 标记当前的点是否被踢出
for(int i = 0; i < n; i++)
{
int index = 0;//标定源顶点
int min;//min 初始化为极大值 INF(即 0x7fffffff),表示“未找到有效节点”
for (int j = 0; j < n; j++)
{
if (!vis[j] && dist[j] < min)//找出未被标记过的源顶点以及距离最近的节点
{
min = dist[j];
index = j;
}
}
vis[index] = true;
for (int h = 0; h < n; h++)
{
//限定条件可以再补充一个if (grid[u][v] != INF && vis[v] == false){}
if (dist[index] + grid[index][h] < dist[index])//及时更新最小距离并存储
{
dist[index] = dist[index] + grid[index][h];
}
}
}
5.Floyd 算法(模板)(处理多源最短路径)
// 开始 floyd
for (int k = 1; k <= n; k++) {//遍历各个节点来寻找最短距离
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
grid[i][j] = min(grid[i][j], grid[i][k] + grid[k][j]);
}
}
}
6.并查集(模板)(寻找元素之间的关系)——判断两个元素是否在同一个集合里的时候
#include <iostream>
using namespace std;
const int MAXN = 1e3;
int pa[MAXN];
int find(int x)//3.查找对应的祖先,即最后输出结果
{
if (x == pa[x])return x;
else {
pa[x] = find(pa[x]);//父节点设为根节点,路径压缩
return pa[x];
}
}
void join(int i, int j)//2.添加元素之间的关系
{
int v = find(i);
int u = find(j);
if (v == u)return;
pa[v] = u;//将i的祖先指向j的祖先
}
void init(int n)//1.初始化节点
{
for (int i = 1; i <= n; i++)
{
pa[i] = i;
}
}
6.连通分量是图论中的核心概念,指一个无向图中,任意两个节点之间都存在路径的 最大子图。
若图中有3个互不连通的子图,则连通分量数为3。
计算方法:
1. 深度优先搜索(DFS)
适用场景:节点数较少(如n ≤ 1e4),代码简单直观。
实现步骤:
- 初始化访问标记数组
visited[]
(全为0)。- 遍历所有未访问节点,对每个节点启动DFS。
- 每次DFS完成:连通分量数+1。
void dfs(int u, vector<bool>& visited, vector<vector<int>>& graph) { visited[u] = true; for (int v : graph[u]) { if (!visited[v]) dfs(v, visited, graph); } } //遍历整个图的节点 int countComponents(int n, vector<vector<int>>& edges) { vector<vector<int>> graph(n); vector<bool> visited(n, false); int cnt = 0; // 建图 for (auto& e : edges) { graph[e[0]].push_back(e[1]); graph[e[1]].push_back(e[0]); } // 计算连通分量 for (int i = 0; i < n; i++) { if (!visited[i]) { dfs(i, visited, graph); cnt++; } } return cnt; }
简化版本:
void dfs(int x) { v[x]=1; for(int i=0;i<n;i++) { if(!v[i]&&g[x][i]==1) { dfs(i); } } } int ltfl()//求取其中的联通分量 { int cnt=0; memset(v,0,sizeof(v));//重点 for(int i=0;i<n;i++) { if(!v[i]) { dfs(i); cnt++; } } return cnt; }
2. 广度优先搜索(BFS)
适用场景:避免递归栈溢出风险(大规模图)。
核心逻辑:用队列代替递归栈,逐层遍历邻接节点。
时间复杂度:与DFS相同(O(V+E))。3. 并查集(Union-Find)
适用场景:动态连通性问题(支持合并操作的高效处理)。
实现步骤:
- 初始化父节点数组
parent[]
(每个节点指向自身)。- 遍历所有边,合并两个端点所属集合。
- 统计根节点数量(即连通分量数)。
vector使用时注意
1.vector<int> v[maxn]
与 vector<int> v(maxn)
的对比
声明方式数据结构类型内存分配位置元素类型
vector<int> v[maxn]
静态数组:包含maxn
个vector<int>
对象栈内存(自动存储期)每个元素是独立的
图的邻接表存储 vector<int> v[maxn]
每个节点对应独立的邻接链表(矩阵)
vector
vector<int> v(maxn)
单个动态数组:一个vector
对象,内含maxn
个int
元素堆内存(动态分配)所有元素为int
类型
预分配固定长度的一维数组 vector<int> v(maxn)
内存连续,适合数值计算,常用一维数组
- 前者创建多个容器对象(数组),后者创建单个容器对象并初始化其元素数量
- 示例:若
maxn=3
,则:
v[maxn]
:3个独立vector
(v[0]
、v[1]
、v[2]
),每个初始为空v(maxn)
:1个vector
,包含3个默认初始化的int
元素(值为0)
利用
vector<int> v[maxn]
时候使用v[a].push_back(a1)时遍历注意:for(int j=0;j<v[a].size();j++){//看看是不是敌对关系 if(v[a][j]==b) flag=0; }
2.vector的resize使用方式
作用:动态调整容器中元素数量(size
),可扩展或缩减容量。
void resize(size_type n); // 默认填充值初始化元素
void resize(size_type n, const T& val); // 用指定值val填充新增元素
vector<int> vec = {1,2,3};
vec.resize(5); // 变为 {1,2,3,0,0}(int默认0)
vec.resize(2); // 变为 {1,2}
vec.resize(4, 10); // 变为 {1,2,10,10}
set功能
set功能:有序性和唯一性 而vector不具有其功能
1.C++标准库中的
set
容器具有自动排序功能。其存储的元素会默认按升序排列(从小到大),且排序规则可通过自定义比较函数调整
2.去重存储:自动过滤重复元素
- 原理:
set
是C++标准库中的有序关联容器,通过红黑树实现,自动保证元素的唯一性。- 代码行为:当执行
s.insert(z)
时,若z
已存在,set
会拒绝插入新副本。- 示例:输入序列
[5, 5, 3, 3, 3]
,set
中仅存储{3, 5}
。
2.std::greater
是C++标准库提供的比较器,可以直接用于实现降序排序。
(如有其他元素可以自定义排序)
#include <iostream>
#include <set>
#include <functional> // 包含 greater
using namespace std;
int main() {
// 使用 greater 实现降序集合
set<int, greater<int>> descendingSet;
descendingSet.insert(10);
descendingSet.insert(30);
descendingSet.insert(20);
// 遍历输出(降序)
for (int val : descendingSet) {
cout << val << " "; // 输出结果:30 20 10
}
return 0;
}
3.set的插入只有s.insert(),无其他的插入方法(不像vector除了v.push_back,或者cin>>v[i]一类的插入方式)
s.size()的作用是得出集合中的元素个数。
s.empty()是判断该集合是否为空。
s.clear()是用来清空集合中的元素。
s.count()是查找出现在集合中的元素的个数,但是由于set有唯一性,一般值为1或0,类似于s.find()判断是否在该集合中出现
s.erase()函数是用来删除指定的元素,只能删除目标值,只能删除一个元素
遍历元素的方式
for (auto it = a.begin(); it != a.end(); it++) { cout << *it << " ";//这里it前面的*我们就把它理解成指针 } for (auto it : a) { cout << it << " "; }
map使用时注意的点
map<int,double>mp(10001); // map构造函数不接受容量参数
map
初始化错误后果:编译报错(
no matching constructor
)。
修复:删除无效参数:
小知识点
1. __gcd(x, y)
求两个数的最大公约数,如__gcd(6, 8) 就返回2。在 algorithm 库中。
2.灵活运用i%2这种处理循环问题(滚动数组)(买卖股票的最佳时机)---约瑟夫环问题
10.vector<string> a如果想要询问存入元素的长度可以使用a[i].size()或a[i].length()
3.在输入getline(cin,a)之前必须记得要输入getchar()来过渡
在主函数中,读取n之后使用getchar()是为了吸收换行符,因为cin >> n会留下换行符在输入流中,如果不处理,接下来的getline会读取到空行。
4.九宫格PAT天梯赛 L1-104九-团体程序设计天梯赛(看点:九宫格遍历方式)图2为暴力法
#include<bits/stdc++.h>
using namespace std;
const int N = 11;//防止在下面越界访问
const int n = 9;//九宫格的范围1-9
int a[N][N];
int vis[N];//记忆化数组,防止出现重复数字
int gp[3][2] = {
{1,3},{4,6},{7,9}};//便捷遍历数组1-3,4-6,7-9
void solve(){
int f = 1; //标记该九宫格是否合法
for(int i = 1;i <= n;i++){
memset(vis,0,sizeof(vis));
for(int j = 1;j <= n;j++){
cin >> a[i][j];
if(a[i][j] >= 10 || a[i][j] <= 0){ //判断输入是否合法
f = 0;//切记不能忘记
continue; //防止在下面越界访问
}
if(vis[a[i][j]])f = 0; //判断每一行是否合法
else vis[a[i][j]] = 1;
}
}
if(f){
//判断每一列是否合法,巧妙利用i与j的位置关系进行与列的遍历
for(int j = 1;j <= n;j++){
memset(vis,0,sizeof(vis));
for(int i = 1;i <= n;i++){
if(vis[a[i][j]])f = 0;
else vis[a[i][j]] = 1;
}
}
}
if(f){
//判断每一宫格是否合法
for(int g = 0;g < n;g++){//表示九宫格的个数1-9
memset(vis,0,sizeof(vis));
for(int i = gp[g / 3][0];i <= gp[g / 3][1];i++){//行优先从1-3只有g从4开始才会到4-6
for(int j = gp[g % 3][0];j <= gp[g % 3][1];j++){//列优先从1-9遍历时刻随着g变化
if(vis[a[i][j]]) f = 0;
else vis[a[i][j]] = 1;
}
}
}
}
if(f)cout << "1" << endl;
else cout << "0" << endl;
}
int main(){
int t;cin >> t;
while(t--){
solve();
}
return 0;
}
for (int i = 0; i < 3; i++)//利用总和统计结果
{
for (int j = 0; j < 3; j++)
{
int sum = 0;
for (int x = 0; x < 3; x++)
{
for (int y = 0; y < 3; y++)
{
sum += a[i * 3 + x][j * 3 + y]; //这里运用四层循环依次遍历,可以代入数据自己模拟一遍来体会此技巧
}
}
if (sum != 45) //每三行三行三列为一个小九宫格,判断一次
{
flag = 0;
break;
}
}
}
5.unordered_map<int,int>a || a[i].count(t2)
a是一个包含n个unordered_map<Int,int>类型元素的数组,a[i]表示访问数组a中第i个 unordered_map容器。(涉及到哈希函数) 而count统计键出现的次数,只会返回1或0.
map<int,int>a || a.count(t2) 其中count统计键出现的次数,不需要a[i]前缀
unordered_map一定要注意要键值对,同时要考虑是第几对容器问题
6. ceil()向上取整 eg:ceil(2.8)->3
7.min=INT_MAX,max=0一般两个数取的值
const int inf=0x3f3f3f3 十六进制的无穷大
9.vector<queue<char>> a(n+1) 单一动态数组:
一个vector
对象,内含 n+1
个 queue<char>
元素
vector<queue<char>> a[n+1](x)
10.遍历stack或queue里面的元素时必须要使用while(!a.empty())
不使用for遍历容器的原因
queue::pop()
会减少队列长度,导致q1.size()
在循环中持续变化for
循环的终止条件i < q1.size()
每次比较时,右侧值逐步递减,而左侧i
递增,二者呈收敛趋势
11.排序思想:可以利用“-”号灵巧的sort()不需要重载,之后再输出原来的结果
【GPLT】【2021天梯赛真题题解】【231分】(L2-3清点代码库)
12.确定行还是列的问题
小技巧:行放行上,列放列上,剩下依据情况分析
vector<vector<int>> grid(n, vector<int>(m, 0));
for (int i = 0; i < n; i++) {//表示行
for (int j = 0; j < m; j++) {//表示列
cin >> grid[i][j];
}
}
// 从左侧边,和右侧边 向中间遍历
for (int i = 0; i < n; i++) {//列的元素对应行遍历
if (grid[i][0] == 1) dfs(grid, i, 0);//第i行第0列(即矩阵的第一列元素)
if (grid[i][m - 1] == 1) dfs(grid, i, m - 1);//第i行第m-1列(即矩阵的最后一列元素)
}
// 从上边和下边 向中间遍历
for (int j = 0; j < m; j++) {//行的元素对应列的遍历
if (grid[0][j] == 1) dfs(grid, 0, j);//第0行第j列元素(即矩阵的第一行元素)
if (grid[n - 1][j] == 1) dfs(grid, n - 1, j);//第n-1行第j列元素(即矩阵的最后一行元素)
}
13.使用string的时候一定要记得灵活运用string的字典序排列。
使用范围举例:比较日期时间
14存储数据灵活使用map,尤其是多次重复在一个堆内叠加数据,map的排序只能在键中排序,如果是值排序的利用vector+sort方式
【全网最细PAT题解】【PAT乙】1032 挖掘机技术哪家强(比较经典的map导vector)
15.连续因子数(L1-006)记住模板
16.int isalnum(int c);
- 所属头文件:C语言
<ctype.h>
/ C++<cctype>
- 核心功能:
判断参数c
是否为字母(A-Z, a-z) 或 数字(0-9)。
- 若满足条件,返回非零值(具体值取决于编译器实现,通常为1)。
- 否则返回 0。
eg; while (isalnum(c = get_next_char()) || c == '_') { ... }
17.to_string
用于将数值类型或部分内置类型转换为对应的字符串形式
- 支持类型:
- 整型:
int
,long
,long long
及其unsigned
版本。- 浮点型:
float
,double
,long double
。- 不支持类型:
char
(需用std::string(1, c)
或std::to_string(static_cast<int>(c))
)。- 字符串字面量(如
const char*
,需直接构造std::string
)。
#include <string>
string str = to_string(42); // 结果:"42"
18.it->a与it.a的区别
it->a
:
- STL迭代器:如
map<int, string>::iterator it
,访问it->first
。- 智能指针:如
std::unique_ptr<Data> ptr
,访问ptr->a
。it.a
:
- 对象成员访问:如结构体变量、类实例。
- 引用类型:如
Data& ref = obj; ref.a = 30;
19.两种迭代器的区别辨析 (传统,范围)
特性 | for(auto it=a.begin(); it!=a.end(); it++) | for(auto it : a) |
---|---|---|
it 的类型 | 迭代器(如 vector<int>::iterator ) | 元素值或引用(取决于 auto 修饰符) |
访问元素方式 | 需解引用(*it ) | 直接访问 it |
修改原容器元素 | 支持(通过 *it = value ) | 若用 auto& it 支持,否则仅副本 |
适用场景 | 需要索引/复杂迭代逻辑(如删除元素) | 简单遍历只读或需简化代码时 |
20.strcmp字符
strcmp
是 C/C++ 中用于比较两个字符串的库函数,其核心功能是通过 逐字符 ASCII 码值比较 来确定字符串的大小关系。#include <string.h> int strcmp(const char *str1, const char *str2);
- 参数:
str1
和str2
是要比较的两个字符串,需以\0
结尾。- 返回值:
- 0:两字符串完全相等。
- 正整数:
str1
大于str2
。- 负整数:
str1
小于str2
.