跳到主要内容算法竞赛基础:递推、递归、搜索与高精度 | 极客日志C++算法
算法竞赛基础:递推、递归、搜索与高精度
本文总结了算法竞赛中的核心知识点,包括递推与递归的思想及代码实现、广度优先搜索(BFS)与深度优先搜索(DFS)的基础模板及应用、迷宫问题的解法以及高精度整数运算。内容涵盖状态转移方程、终止条件寻找、图遍历模板及数组模拟计算等关键技巧,适合初学者入门学习。
虚拟内存1 浏览 递推和递归
递推
先简单介绍一下
递推算法:自底向上,由已知条件推导出位置结果的一种算法。
核心:状态转移(边界 + 关系式)
废话不多说,直接从题目入手理解递推思想。
选用信息学奥赛一本通的题:卒过河问题。
题目意思理解:
从 A 点到 B 点的路径数,并且经过马能到达的点都无效,我们先做好前期的工作准备。
int mx[] = {-1,-1,-2,-2,1,1,2,2};
int my[] = {-2,2,-1,1,-2,2,-1,1};
int vis[110][110]={0};
int rd[110][110]={0};
for(int i=0;i<=7;i++) {
int nx=cx+mx[i];
int ny=cy+my[i];
if(nx<0 || ny<0 || nx>n || ny>m)continue;
vis[nx][ny]=1;
}
vis[cx][cy]=1;
然后我们准备初始化边界,满足
rd[0][]=;
( i=;i<=n;i++) {
rd[i][]=rd[i][];
(vis[i][]==)rd[i][]=;
}
( i=;i<=m;i++) {
rd[][i]=rd[][i];
(vis[][i]==)rd[][i]=;
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown 转 HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
- HTML 转 Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
0
1
for
int
1
0
-1
0
if
0
1
0
0
for
int
1
0
0
-1
if
0
1
0
0
注意题目,棋子卒能挪动的方向只有向下和向右,说明在某个点 K 上,到达 K 的路径数为 K 上面的点 +K 左面的点。
所以我们可以列出关系式:rd[i][j] = rd[i-1][j]+rd[i][j-1]
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
if(vis[i][j]==1)continue;
rd[i][j]=rd[i-1][j]+rd[i][j-1];
}
}
#include<bits/stdc++.h>
using namespace std;
#define int long long
int mx[] = {-1,-1,-2,-2,1,1,2,2};
int my[] = {-2,2,-1,1,-2,2,-1,1};
int vis[110][110]={0};
int rd[110][110]={0};
signed main(){
int n,m,cx,cy;
cin>>n>>m>>cx>>cy;
vis[cx][cy]=1;
for(int i=0;i<=7;i++){
int nx=cx+mx[i];
int ny=cy+my[i];
if(nx<0 || ny<0 || nx>n || ny>m)continue;
vis[nx][ny]=1;
}
rd[0][0]=1;
for(int i=1;i<=n;i++){
rd[i][0]=rd[i-1][0];
if(vis[i][0]==1)rd[i][0]=0;
}
for(int i=1;i<=m;i++){
rd[0][i]=rd[0][i-1];
if(vis[0][i]==1)rd[0][i]=0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(vis[i][j]==1)continue;
rd[i][j]=rd[i-1][j]+rd[i][j-1];
}
}
cout<<rd[n][m]<<endl;
return 0;
}
递推有两要素:边界条件和关系式。
只要正确推出这两个要素,再从已知推未知,就可以轻松解决递推问题啦。
递归
递归的经典引入
递归算法:直接或间接地调用自身函数,循环回溯解决小问题,最后得出大问题的答案。
核心思想:找到终止条件(没有终止条件的递归会陷入无限循环)。
我们还是结合经典例题来理解递归思想,我将先讲述一个简单的例题来让大家理解。
理解题目意思:
斐波那契数列我们应该非常熟悉了,就是 f(n) = f(n-1)+f(n-2) , 其中 n != 1 ,n != 2。
所以在这道题中,我们的必备公式就是:f(n) = f(n-1)+f(n-2)
放到函数中,就是让当前第 n 位函数回过头来调用它之前的两个函数,再让第 n-1 位函数回过头来调用它之前的两个函数,再让第 n-2 个函数......直到调用到最底层函数(也就是第一位函数),再一层一层返回结果。
int fb(int x){
if(x==1 || x==2)return 1;
return fb(x-1)+fb(x-2);
}
#include<bits/stdc++.h>
using namespace std;
int fb(int x){
if(x==1 || x==2)return 1;
return fb(x-1)+fb(x-2);
}
int main(){
int n;
cin>>n;
while(n--){
int num;
cin>>num;
cout<<fb(num)<<endl;
}
}
看到这里,大家应该对递归已经有了初步了解,接下来我们再深入理解一下。
递归的深入理解
这里选用的是蓝桥杯省 A 组的真题:正则表达式匹配长度问题。
题目理解:
正则表达式中的'( )'以及括号内容物可以看成一组。
' | '可以看成分界线,分界线两边的字符串长度互不干扰且相互独立。
' x '就是普通的一个字符。
((xx|xxx)x|(x|xx))xx
第一步:先拆分成组,寻找第一组
则第一组为 (xx|xxx)
将' | '看成分界线,左右两边的字符串长度分别是 2 和 3,所以取最大为 3
第二步:往下正常找寻第二组
往下接着看 (xx|xxx)x 遇见普通字符 x,直接长度 +1
紧接着遇见' | ',看成分界线,左边字符串长度为 4 即 (xx|xxx)x
右边找到第二组 (x|xx)
沿用上面的方法,这组字符串的最大长度为 2
所以这个分界线得到的最大长度是 4
第三步:接着找组
找到 ((xx|xxx)x|(x|xx)) 为一组
已经算出这组的结果为 4
最后加上 xx 两个普通字符的长度
((xx|xxx)x|(x|xx))xx 的能接受的最长字符串长度为 6
可以看出,我们的中心思想就是不断找组,然后根据不同符号进行不同判断。
因为这个代码较为完整,不好拆分,所以我直接将完整代码贴给大家,并标明注释。
#include<bits/stdc++.h>
using namespace std;
int i=0;
string s;
int Longest(){
int m_cnt=0;
int cnt=0;
while(i < s.size()){
if(s[i] == '('){
i++;
int n = Longest();
cnt += n;
} else if(s[i] == 'x'){
cnt++;
i++;
} else if(s[i] == '|'){
m_cnt = max(m_cnt,cnt);
cnt=0;
i++;
} else {
i++;
break;
}
}
return max(cnt,m_cnt);
}
int main(){
cin>>s;
cout<<Longest();
}
递归最重要的一步:找到终止条件。
我们找到式子之间的关系,再循环调用就可以解决问题啦。
bfs 广度优先搜索
基础模板
bfs:是一种图的遍历算法,就是找到一个节点的相邻节点,再找相邻节点的相邻节点。
核心思想:从浅到深,宽度遍历。
从一个节点开始,找它的邻居,再找邻居的邻居....
bfs 算法这里我会给两种基础模板
在不同情况下使用
用邻接矩阵实现时,它的结构较为简单,适合初学者去理解,但是空间复杂度大,
而且二维数组不能存储过大的数据
当遇见数据量庞大的时候,我们就可以选择邻接表来实现 bfs 了
临界矩阵实现 bfs (代码加详细注释已给出)
注意:此模板是在无向图中,无向图有 n 个点,m 条边
#include<bits/stdc++.h>
using namespace std;
int g[105][105];
int vis[105];
int n,m;
void bfs(int start){
queue<int> q;
q.push(start);
vis[start] = 1;
while(!q.empty()){
int cur = q.front();
q.pop();
for(int i=1;i<=n;i++){
if(g[cur][i] == 1 && vis[i] == 0){
vis[i] = 1;
q.push(i);
}
}
}
}
int main(){
cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v;
cin >> u >> v;
g[u][v] = 1;
g[v][u] = 1;
}
bfs(1);
return 0;
}
邻接表实现 bfs
注意:此模板是在无向图中,无向图有 n 个点,m 条边
#include<bits/stdc++.h>
using namespace std;
vector<int> adj[105];
int vis[105];
int n,m;
void bfs(int start){
queue<int> q;
q.push(start);
vis[start] = 1;
while(!q.empty()){
int cur = q.front();
q.pop();
for(int i=0;i<adj[cur].size();i++){
int nxt = adj[cur][i];
if(vis[nxt] == 0){
vis[nxt] = 1;
q.push(nxt);
}
}
}
}
int main(){
cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
bfs(1);
return 0;
}
bfs 最短路问题
我们根据具体问题,理解一下 bfs 的最短路题型。
题目很简单,在无向图中,假设存在两个点 a,b,求 a 点到 b 点的最短路径
#include<bits/stdc++.h>
using namespace std;
vector<int> adj[105];
int vis[105];
int n,m;
int a,b;
struct node{
int id;
int step;
};
void bfs(int start){
queue<node> q;
node tmp;
tmp.id = start;
tmp.step = 0;
q.push(tmp);
vis[start] = 1;
while(!q.empty()){
int curId = q.front().id;
int curStep = q.front().step;
q.pop();
for(int i=0;i<adj[curId].size();i++){
node nxt;
nxt.id = adj[curId][i];
nxt.step = curStep + 1;
if(nxt.id == b){
cout << nxt.step << endl;
return;
}
if(vis[nxt.id] == 0){
vis[nxt.id] = 1;
q.push(nxt);
}
}
}
}
int main(){
cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
cin >> a >> b;
bfs(a);
return 0;
}
相信看到这里,大家对 bfs 算法已经有了一定的了解,接下来我会介绍 dfs 算法,并在期间穿插讲解 bfs 真题,帮助大家更深入的理解。
dfs 深度优先搜索
基础模板
dfs 算法:纵向搜索,深入搜索,尽可能深的探入搜索,直到无法搜索,然后回溯。
这里仍旧是两种实现方法
两种方法的优势同 bfs 算法,有忘记的请移步 bfs 基础模板章节
就个人而言,我认为 dfs 算法的模板更好理解,更简洁一点,但是大家不要被里面的逻辑绕晕了。
先给出邻接矩阵实现 dfs 的基础模板,这里直接给出代码配以详细注释
(n 个点,m 条边的无向图)
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
int a[N][N];
int vis[N];
void dfs(int cur){
vis[cur]=1;
for(int i=1;i<=n;i++){
if(a[cur][i]==1 && vis[i]==0){
dfs(i);
}
}
}
int main(){
cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v;
cin >> u >> v;
a[u][v]=1;
a[v][u]=1;
}
dfs(1);
return 0;
}
邻接表实现 dfs 的基础模板,这里直接给出代码配以详细注释
(n 个点,m 条边的无向图)
#include<bits/stdc++.h>
using namespace std;
const int N=1000;
vector<int>a[N];
int vis[N];
void dfs(int cur){
vis[cur]=1;
for(int i=0;i<a[cur].size();i++){
int nxt=a[cur][i];
if(vis[nxt]==0){
dfs(nxt);
}
}
}
int main(){
cin >> n >> m;
for(int i=1;i<=m;i++){
int u,v;
cin >> u >> v;
a[u].push_back(v);
a[v].push_back(u);
}
dfs(1);
return 0;
}
迷宫问题
dfs 版
简单介绍一下什么是迷宫问题:通常题型是要求从某点走到终点,期间有阻碍和正常通路,我们要做的就是判断能否走到终点。
从 A 点到达 B 点,期间可以上下左右移动,但是只能走'路',不能'撞墙',有多组测试数组。
开始答题
第一步:
我们把输入问题解决了,
已经知道有 k 组,其实直接 while 循环 + k--即可
如果你想要代码高级一点也可以格外设置一个输入函数,这里不做示例
并且我们要创建一个二维字符数组,用来存储迷宫
这里采用普通数组矩阵,我来解答一下为什么不用邻接表来解决这个问题?
还是哪个问题,我们有多组输入
如果我们用邻接表动态数组来做,除了第一组数据外的其他迷宫数据会直接填入,而不是清空后再填入
用邻接矩阵就很好地解决了这个问题,在输入新数据时,旧数据直接被清空了
然后我们还需要一个标记数组,标记这段路我们走过了,因为可以上下左右移动,如果不标记会反复移动在可以走的路的区间内,造成死循环
int n;
char maze[110][110];
int vis[110][110];
int xA,yA,xB,yB;
int nx[]={-1,0,0,1};
int ny[]={0,-1,1,0};
int k;
cin>>k;
while(k--){
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
char c;
cin>>c;
maze[i][j]=c;
vis[i][j]=0;
}
}
第二步:
写 dfs 解决(其中一个解法)
这里结合代码和注释来直接给大家
bool dfs(int sx,int sy){
int cur_x=sx;
int cur_y=sy;
vis[cur_x][cur_y]=1;
if(cur_x == xB && cur_y == yB)return true;
for(int i=0;i<4;i++){
int nxt_x=cur_x+nx[i];
int nxt_y=cur_y+ny[i];
if(judge(nxt_x,nxt_y) && vis[nxt_x][nxt_y]==0){
if(dfs(nxt_x,nxt_y))return true;
}
}
return false;
}
第三步:
判断的书写
相信大家在浏览上述代码的时候注意到了 judge() 函数
现在我来讲述一下这个判断函数的意义:
1.保证搜寻的点在迷宫内,没有出界限
2.保证走到的是路而不是墙
bool judge(int x,int y){
if(x<0 || y<0 || x>=n || y>=n)return false;
if(maze[x][y]=='.')return true;
else return false;
}
到这里我们的基本思路已经理顺完成,下面给出完整代码。
#include<bits/stdc++.h>
using namespace std;
int n;
char maze[110][110];
int vis[110][110];
int xA,yA,xB,yB;
int nx[]={-1,0,0,1};
int ny[]={0,-1,1,0};
bool judge(int x,int y){
if(x<0 || y<0 || x>=n || y>=n)return false;
if(maze[x][y]=='.')return true;
else return false;
}
bool dfs(int sx,int sy){
int cur_x=sx;
int cur_y=sy;
vis[cur_x][cur_y]=1;
if(cur_x == xB && cur_y == yB)return true;
for(int i=0;i<4;i++){
int nxt_x=cur_x+nx[i];
int nxt_y=cur_y+ny[i];
if(judge(nxt_x,nxt_y) && vis[nxt_x][nxt_y]==0){
if(dfs(nxt_x,nxt_y))return true;
}
}
return false;
}
int main(){
int k;
cin>>k;
while(k--){
cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
char c;
cin>>c;
maze[i][j]=c;
vis[i][j]=0;
}
}
cin>>xA>>yA>>xB>>yB;
bool ans=dfs(xA,yA);
if(ans)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
return 0;
}
bfs 版
上述 bfs 版本已经可以 AC 了,是不是感觉不错?
迷宫最短路问题:
dfs 需要把所有可能路径罗列出来,指数级复杂度
bfs 第一次走到那个点一定就是最短路,O(n) 复杂度
基本思路是没有任何区别的,我们直接给出代码和注释。
#include<iostream>
#include<queue>
using namespace std;
int n;
char maze[110][110];
int vis[110][110];
int xA, yA, xB, yB;
int nx[] = {-1, 0, 0, 1};
int ny[] = {0, -1, 1, 0};
bool judge(int x, int y) {
if (x < 0 || y < 0 || x >= n || y >= n) return false;
if (maze[x][y] == '.') return true;
else return false;
}
void bfs(int sx, int sy) {
queue<pair<int, int>> q;
q.push({sx, sy});
vis[sx][sy] = 1;
while (!q.empty()) {
int cur_x = q.front().first;
int cur_y = q.front().second;
q.pop();
if (cur_x == xB && cur_y == yB) {
return;
}
for (int i = 0; i < 4; i++) {
int nxt_x = cur_x + nx[i];
int nxt_y = cur_y + ny[i];
if (judge(nxt_x, nxt_y) && vis[nxt_x][nxt_y] == 0) {
q.push({nxt_x, nxt_y});
vis[nxt_x][nxt_y] = 1;
}
}
}
}
int main() {
int k;
cin >> k;
while (k--) {
cin >> n;
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
char c;
cin >> c;
maze[i][j] = c;
vis[i][j] = 0;
}
}
cin >> xA >> yA >> xB >> yB;
bfs(xA, yA);
if (vis[xB][yB]) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
以上就是迷宫问题的基本思路和解法,希望带给大家一点启发。
高精度
定义:用于处理超大整数运算,把整数运算变成数组运算,模拟手工计算的方式。
核心思想:用数组的每一位存储数字的一位,模拟竖式计算。
高精度加法
这里我给出用途比较广泛的高精度加法样例,就是把大整数的每一位存在数组中,再进行计算,具体直接在代码中进行注释。
#include<iostream>
#include<string>
using namespace std;
int a[110];
int b[110];
int c[110];
int main() {
string s1, s2;
cin >> s1 >> s2;
int k = 1, m = 1;
for (int i = s1.size() - 1; i >= 0; i--) {
a[k++] = s1[i] - '0';
}
for (int i = s2.size() - 1; i >= 0; i--) {
b[m++] = s2[i] - '0';
}
int lmax = max(s1.size(), s2.size());
for (int j = 1; j <= lmax; j++) {
c[j] += a[j] + b[j];
c[j + 1] += c[j] / 10;
c[j] %= 10;
}
for (int i = lmax + 1; i >= 1; i--) {
if (i == lmax + 1 && c[i] == 0)continue;
else cout << c[i];
}
return 0;
}