目录
声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。
下方链接为学习笔记目录链接(中转站)
一、递推与递归
对于一个待求解的问题,当它局限于某处边界,某个小范围或者某种特殊情况下时,其答案往往是已知的。如果能够将该解答的应用场景扩大到原问题的状态空间,并且扩展过程的每个步骤都具有相似性,就可以考虑使用递推或者递归求解。
递归问题的核心思想:
在每次递归的时候都分别尝试所有可能的情况,选择分支,将问题数量减少一,从而转化为一个规模更小的同类问题
二、分治
分治法把一个问题划分位=为若干个规模更小的同类子问题,对这些子问题递归求解,然后在回溯时通过它们推导出原问题的解。
三、模拟计算机实现递归
// 模拟机器实现,把组合型枚举改为非递归
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