0x02.基本算法 — 递推与递归

本文是《算法竞赛进阶指南》的学习笔记,重点讲解了递推与递归的概念和应用,包括指数型枚举、组合型枚举、排列型枚举的递归实现,以及分治法。同时,介绍了《挑战程序设计竞赛》中的相关习题,如AcWing 92-95,通过实例解析递归问题的解题思路和优化技巧。
摘要由CSDN通过智能技术生成

声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了 按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。


下方链接为学习笔记目录链接(中转站)

学习笔记目录链接


ACM-ICPC在线模板


一、递推与递归

对于一个待求解的问题,当它局限于某处边界,某个小范围或者某种特殊情况下时,其答案往往是已知的。如果能够将该解答的应用场景扩大到原问题的状态空间,并且扩展过程的每个步骤都具有相似性,就可以考虑使用递推或者递归求解。

递归问题的核心思想:

在每次递归的时候都分别尝试所有可能的情况,选择分支,将问题数量减少一,从而转化为一个规模更小的同类问题

在这里插入图片描述

二、分治

分治法把一个问题划分位=为若干个规模更小的同类子问题,对这些子问题递归求解,然后在回溯时通过它们推导出原问题的解。

三、模拟计算机实现递归

// 模拟机器实现,把组合型枚举改为非递归

vector<int> chosen;
int stack[100010], top = 0, address = 0;

void call(int x, int ret_addr) {
    // 模拟计算机汇编指令call
	int old_top = top;
	stack[++top] = x; // 参数x
	stack[++top] = ret_addr; // 返回地址标号
	stack[++top] = old_top; // 在栈顶记录以前的top值
}


int ret() {
    // 模拟计算机汇编指令ret
	int ret_addr = stack[top - 1];
	top = stack[top]; // 恢复以前的top值
	return ret_addr;
}

int main() {
   
	int n, m;
	cin >> n >> m;
	call(1, 0); // calc(1)
	while (top) {
   
		int x = stack[top - 2]; // 获取参数
		switch (address) {
   
		case 0:
			if (chosen.size() > m || chosen.size() + (n - x + 1) < m) {
   
				address = ret(); // return
				continue;
			}
			if (x == n + 1) {
   
				for (int i = 0; i < chosen.size(); i++)
					printf("%d ", chosen[i]);
				puts("");
				address = ret(); // return
				continue;
			}
			call(x + 1, 1); // 相当于calc(x + 1),返回后会从case 1继续执行
			address = 0;
			continue; // 回到while循环开头,相当于开始新的递归
		case 1:
			chosen.push_back(x);
			call(x + 1, 2); // 相当于calc(x + 1),返回后会从case 2继续执行
			address = 0;
			continue; // 回到while循环开头,相当于开始新的递归
		case 2:
			chosen.pop_back();
			address = ret(); // 相当于原calc函数结尾,执行return
		}
	}
}

四、相应习题:

0.AcWing 92. 递归实现指数型枚举(递归/循环+位运算)

AcWing 92. 递归实现指数型枚举(递归/循环+位运算)
在这里插入图片描述
输出样例:

3
2
2 3
1
1 3
1 2
1 2 3

这个问题等价于每个整数选或者不选,那么总方案数就有 2 n 2^n 2n种。
可以根据上一节学习的状态压缩方法运用二进制来存储状态,做法如下:

#include<bits/stdc++.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=5e5+7;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0
int n;
void dfs(int u, int state)
{
   
    if (u == n)
    {
   
        for (int i = 0; i < n; i ++ )
            if (state >> i & 1)
                cout << i + 1 << ' ';
        cout << endl;
        return;
    }

    dfs(u + 1, state);
    dfs(u + 1, state + (1 << u));
}

int main()
{
   
    cin >> n;
    dfs(0, 0);
    return 0;
}

当然也可以直接递归完成。
在每次递归的时候都分别尝试某个数选或者不选这两种情况(尝试所有可能的状态),选择这两条分支,将问题中的整数数量减少一,从而转化为一个规模更小的同类问题,这就是递归问题的核心思想

#include<bits/stdc++.h>
#define ls (p<<1)
#define rs (p<<1|1)
#define over(i,s,t) for(register long long i=s;i<=t;++i)
#define lver(i,t,s) for(register long long i=t;i>=s;--i)
//#define int __int128
using namespace std;
typedef long long ll;//全用ll可能会MLE或者直接WA,试着改成int看会不会A
const ll N=5e5+7;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0
ll n;
vector<ll>chosen;
void calc(ll x)
{
   
    if(x==n+1){
   
        for(int i=0;i<chosen.size();++i)
            printf("%lld ",chosen[i]);
        puts("");
        return ;
    }
    //不选
    calc(x+1);
    //选
    chosen.push_back(x);
    calc(x+1);
    chosen.pop_back();//准备回溯到上一个问题之前要还原现场
}
int main()
{
   
    scanf("%lld",&n);
    calc(1);
    return 0;
}

1.AcWing 93. 递归实现组合型枚举

AcWing 93. 递归实现组合型枚举
在这里插入图片描述
输出样例:

1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

繁凡さん

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值