目录

  • 一、递推与递归
  • 二、分治
  • 三、模拟计算机实现递归
  • 四、相应习题:
    • 0.AcWing 92. 递归实现指数型枚举(递归/循环+位运算)
    • 1.AcWing 93. 递归实现组合型枚举
    • 2.AcWing 94. 递归实现排列型枚举 (全排列)
    • 3.AcWing 95. 费解的开关
    • 4.奇怪的汉诺塔 (n盘4柱)
    • 4.1奇怪的汉诺塔++(n盘m柱)
    • 5.POJ 1845 Sumdiv(AcWing 97. 约数之和)(数论)(分治)
    • 6.POJ 3889 Fractal Streets(AcWing 98. 分形之城)(分形,递归,DFS)

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


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

学习笔记目录链接


ACM-ICPC在线模板


一、递推与递归

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

递归问题的核心思想:

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

二、分治

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

三、模拟计算机实现递归

// 模拟机器实现,把组合型枚举改为非递归vector<int> chosen;
int stack[100010], top = 0, address = 0;void call(int x, int ret_addr) { // 模拟计算机汇编指令callint old_top = top;stack[++top] = x; // 参数xstack[++top] = ret_addr; // 返回地址标号stack[++top] = old_top; // 在栈顶记录以前的top值
}int ret() { // 模拟计算机汇编指令retint 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(); // returncontinue;}if (x == n + 1) {for (int i = 0; i < chosen.size(); i++)printf("%d ", chosen[i]);puts("");address = ret(); // returncontinue;}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

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

#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 5
3 4 5

相比上一个问题多加一个判断:如果所选的数超过m个或者剩下的全部选完都不够m个就返回

#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,m;
vector<ll>chosen;
void calc(ll x)
{//多加一个判断,如果所选的数超过m个或者剩下的全部选完都不够m个就返回if(chosen.size()>m||chosen.size()+(n-x+1)<m)return ;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%lld",&n,&m);calc(1);return 0;
}

2.AcWing 94. 递归实现排列型枚举 (全排列)

https://www.acwing.com/problem/content/96/

也可以用C++的STL里面的 next_permutation

#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,m;ll order[20];
bool chosen[20];
void dfs(ll x)
{if(x==n+1){over(i,1,n)printf("%lld%s",order[i],i==n?"\n":" ");//这里应该用%s(前后要对应)return ;}over(i,1,n)//对于每一位都有n种选择{if(chosen[i])continue;order[x]=i;chosen[i]=1;dfs(x+1);chosen[i]=0;//回溯order[x]=0;//这一行可忽略,毕竟一会还是会被覆盖的}
}int main()
{scanf("%lld",&n);dfs(1);return 0;
}

3.AcWing 95. 费解的开关

https://www.acwing.com/problem/content/97/

正解
大致解题思路:这道题目首先看一眼,我们就可以知道必然与位运算有着密切的关系,因为出现了0和1,这是一个重要的发现,接着我们在仔细分析题意,我们知道如果纯暴力枚举的话,必然是会超时的,那么如何优化呢?因此我们需要从题目中找出非常有用的性质来优化,这是一个大致的思路方向

每一个位置顶多只会操作一次。因为如果操作两次的话,相当于不操作,必然是不满足最优解。

在一套方案中,操作的顺序无关紧要,这一个略加思索便可得知

最重要的性质,如果我们确定了第I行的操作方案的话,那么后面的行数都可以依此递推,下面给出一个详细的解答。

11011
10110
01111
11111
比如说这个例子,如果我们确定了第1行,那么第二行所有的0(坐标:a[i][j])
都只能是第三行a[i+1][j]来修改了,因为如果你第二行修改的话,
那么第一行将会打乱,下面每一行依此类推。

懒得写解析了,上面的是直接用大佬的
上述题解来源

所以说,我们只需要得出第一行的顺序,后面的顺序都可以顺序推出,不过第一行必须全部打开

特别注意二进制的第几位是从第0位开始的,一定要注意,本题中如果枚举的j是从1开始的就要j-1

#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=10;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0ll n,m;
char a[N][N];
char b[N][N];
ll ans,answer;
inline void init()
{getchar();over(i,1,5){over(j,1,5){char ch=getchar();a[i][j]=ch-'0';}getchar();}
}
inline bool judge()
{over(i,1,5)over(j,1,5){if(!b[i][j])return false;}return true;
}inline void handle(ll x,ll y)
{b[x][y]^=1;b[x+1][y]^=1;b[x-1][y]^=1;b[x][y+1]^=1;b[x][y-1]^=1;
}
inline ll solve()
{answer=1e9+7;for(int i=1;i<=(1<<5);++i)//枚举所有可能的情况,比较。{//一定要先把第一行给全部打开,但是如果遇见一个没有开的就直接开不一定能实现全部打开,ans=0;//所以要枚举所有的可能使得第一行的灯全部打开memcpy(b,a,sizeof a);over(j,1,5){if(i>>(j-1)&1)//特别注意二进制的第几位是从第0位开始的,一定要减一ans++,handle(1,j);}over(j,1,4)over(k,1,5)//由第一行的状态决定后面所有行的情况{if(!b[j][k])//如果这一位没有开灯就直接开ans++,handle(j+1,k);}if(judge()){answer=min(ans,answer);}}return answer;
}
ll t;
int main()
{scanf("%lld",&t);while(t--){init();if(solve()<=6)printf("%lld\n",answer);else puts("-1");}return 0;
}

4.奇怪的汉诺塔 (n盘4柱)

https://www.acwing.com/problem/content/98/

我们先考虑三个塔的汉诺塔问题,最优秀方案:必然是先挪走n-1个圆盘,然后再挪走圆盘N,
因此可以得出递推方程也就是 d[i]=d[i−1]∗2+1d[i]=d[i-1]*2+1d[i]=d[i−1]∗2+1
之所以要乘以2,是因为第一次挪到第二个塔,然后还要挪移回到第三个塔,下面四个塔也是这样的
接着考虑四塔问题,我们可以这么思考,首先挪走j个塔,也就是有四个塔可以选择,然后再挪走剩下的n-j个塔,此时有三个塔可以选择,因此这就是我们的状态转移方程:f[i]=min(f[i],f[j]∗2+d[n−j])f[i]=min(f[i],f[j]*2+d[n-j])f[i]=min(f[i],f[j]∗2+d[n−j]),i表示当前一共有几个塔,也就是上文所说的n

#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 d[20],f[20];
int main()
{d[1]=1;for(int i=2;i<=12;++i)d[i]=d[i-1]*2+1;memset(f,0x3f,sizeof f);f[0]=0;for(int i=1;i<=12;i++)for(int j=0;j<=i;++j)f[i]=min(f[i],f[j]*2+d[i-j]);over(i,1,12)printf("%lld\n",f[i]);return 0;
}

4.1奇怪的汉诺塔++(n盘m柱)

上面的那道的进化版本,这次要求n盘m柱的汉诺塔最小移动次数

类比只有三个柱子的汉诺塔, 设 f[i][j]f[i][j]f[i][j] 为有iii个盘子 jjj 个柱子时的最少步数. 那么肯定是把一些上面盘子移动到某根不是 jjj 的柱子上, 然后把剩下的盘子移动到 jjj , 然后再把上面的盘子移动到 jjj . 于是就有递推式
f[i][j]=minf[k][j]∗2+f[i−k][j−1]f[i][j]=minf[k][j]∗2+f[i−k][j−1]f[i][j]=minf[k][j]∗2+f[i−k][j−1]
代码如下:

#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=5e2+7;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0int n,m;
ll dp[N][N];int main()
{scanf("%d%d",&n,&m);memset(dp,0x3f,sizeof dp);dp[3][0]=0;for(int i=1;i<=n;++i) dp[3][i]=dp[3][i-1]<<1|1;//*2+1for(int i=4;i<=m;++i) dp[i][0]=0,dp[i][1]=1;
/*  for(int i=2;i<=n;++i)for(int j=1;j<i;++j)dp[4][i]=min(dp[4][i],2*dp[4][i-j]+mpow(2,j)-1);*/for(int i=4;i<=m;++i)for(int j=2;j<=n;++j)for(int k=1;k<j;++k) dp[i][j]=min(dp[i][j],dp[i][j-k]*2+dp[i-1][k]);printf("%lld\n",dp[m][n]);return 0;
}

5.POJ 1845 Sumdiv(AcWing 97. 约数之和)(数论)(分治)

本题需要运用到一下数论知识点。

(1) 整数的唯一分解定理:

任意正整数都有且只有一种方式写出其素因子的乘积表达式。

A=(p1k1)∗(p2k2)∗(p3k3)∗....∗(pnkn)A=(p1^k1)*(p2^k2)*(p3^k3)*....*(pn^kn)A=(p1k1)∗(p2k2)∗(p3k3)∗....∗(pnkn) 其中pi均为素数

(2) 约数和公式:

对于已经分解的整数A=(p1k1)∗(p2k2)∗(p3k3)∗....∗(pnkn)A=(p1^{k1})*(p2^{k2})*(p3^{k3})*....*(pn^{kn})A=(p1k1)∗(p2k2)∗(p3k3)∗....∗(pnkn)

有A的所有因子之和为

S=(1+p1+p12+p13+...p1k1)∗(1+p2+p22+p23+….p2k2)∗(1+p3+p33+…+p3k3)∗....∗(1+pn+pn2+pn3+...pnkn)S = (1+p1+p1^2+p1^3+...p1^{k1}) * (1+p2+p2^2+p2^3+….p2^{k2}) * (1+p3+ p3^3+…+ p3^{k3}) * .... * (1+pn+pn^2+pn^3+...pn^{kn})S=(1+p1+p12+p13+...p1k1)∗(1+p2+p22+p23+….p2k2)∗(1+p3+p33+…+p3k3)∗....∗(1+pn+pn2+pn3+...pnkn)

(3) 同余模公式:

(a+b)%m=(a%m+b%m)%m(a+b)\%m=(a\%m+b\%m)\%m(a+b)%m=(a%m+b%m)%m

(a∗b)%m=(a%m∗b%m)%m(a*b)\%m=(a\%m*b\%m)\%m(a∗b)%m=(a%m∗b%m)%m

答案详解:
https://www.acwing.com/solution/acwing/content/813/
以后学完数论再写这道题吧。

6.POJ 3889 Fractal Streets(AcWing 98. 分形之城)(分形,递归,DFS)


题解l链接:
https://www.acwing.com/solution/AcWing/content/814/
如下图:

我的代码:

#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=5e2+7;
const ll mod=1e9+7;
const double EPS=1e-10;//-10次方约等于趋近为0
pair<ll,ll> calc(ll n,ll m)
{if(n==0)return make_pair(0,0);ll len=1ll<<(n-1),cnt=1ll<<(2*n-2);pair<ll,ll> pos=calc(n-1,m%cnt);ll x=pos.first,y=pos.second;ll z=m/cnt;if(z==0)return make_pair(y,x);if(z==1)return make_pair(x,y+len);if(z==2)return make_pair(x+len,y+len);if(z==3)return make_pair(2*len-y-1,len-x-1);
}ll t;
int main()
{scanf("%lld",&t);while(t--){ll n,a,b;scanf("%lld%lld%lld",&n,&a,&b);pair<ll,ll> ma=calc(n,a-1);pair<ll,ll> mb=calc(n,b-1);ll x=ma.first-mb.first,y=ma.second-mb.second;double dir=sqrt(double(x*x+y*y))*10;printf("%0.f\n",dir);}return 0;
}

注:如果您通过本文,有(qi)用(guai)的知识增加了,请您点个赞再离开,如果不嫌弃的话,点个关注再走吧,日更博主每天在线答疑 ! 当然,也非常欢迎您能在讨论区指出此文的不足处,作者会及时对文章加以修正 !如果有任何问题,欢迎评论,非常乐意为您解答!( •̀ ω •́ )✧

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

  1. 算法设计与分析 1 递推与递归

    递推与递归 Fibonacci #include<stdio.h> #pragma warning (disabled:4996) #define MAX 100 int f[MAX];/ ...

  2. 基本算法之递推与递归的简单应用

    递推与递归的简单应用 常见的枚举形式 实现指数型枚举 DFS (一) DFS (二) 位运算(一) 位运算(二) 实现组合型枚举 DFS + 剪枝 实现排列型枚举 DFS 费解的开关 奇怪的汉诺塔 分 ...

  3. 算法总结之递推与递归

    递推算法 递归算法大致包括两方面的内容:1)递归起点 : 2)递归关系 递推起点 递归起点一般由题目或者实际情况确定,不由递归关系推出.如果无法确定递归起点,那么递归算法就无法实现.可见,递归起点是递 ...

  4. 算法设计分析之------ 递推与递归篇二

    递推与递归篇二 递归 Secret Cow Code S 黑白棋子的移动 递推(动态规划) 覆盖墙壁 递归 Secret Cow Code S 题目链接:Secret Cow Code S 解题思路 ...

  5. 基础算法 —— 递推算法

    [概述] 递推算法:通过已知条件,利用相邻的数据项间的关系(即:递推关系),得出中间推论,直至得到结果的算法. 递推关系:给定一个数的序列H0,H1,-,Hn,若存在整数N0,使当n>N0时,可 ...

  6. matlab如何仿真递推型dft算法,递推dft算法

    0.7 0.8 0.9 1 0.6 0.7 0.8 0.9 1 伯格(Burg)递推算法 L-D算法缺点: 在计算相关函数估计时,对N个观测数据以 外的数据作零的假设,故谱估计误差较...... 第3 ...

  7. 递推与递归 (区别)

    递推与递归 本文中部分内容转自他人博客,作者相关信息以及博客地址在文末. 概念 递归:从已知问题的结果出发,用迭代表达式逐步推算出问题的开始的条件,即顺推法的逆过程,称为递归. 递归的定义:在一个函数 ...

  8. 递推和递归(C语言)

    文章目录 前言 一.递推原理 1.递推概念 2.递推关系 3.递推特点 4.递推详例 5.解决递推问题的步骤 二.递归原理 1.递归的概念 2.构成递归的条件 3.递归的模板 4.递归详例 三.递推和 ...

  9. 昆虫繁殖(继续理解递推和递归)

    昆虫繁殖(继续理解递推和递归) /*[题目描述] 科学家在热带森林中发现了一种特殊的昆虫,这种昆虫的繁殖能力很强.每对成虫过x个月产y对卵,每对卵要过两个月长成成虫.假设每个成虫不死,第一个月只有一对 ...

最新文章

  1. [BZOJ1572][Usaco2009 Open]工作安排Job
  2. 最大隶属度原则_首款骁龙888旗舰亮相?Realme新品,这外观很有辨识度
  3. 同步和异步GET,POST请求
  4. 20151210小问题2
  5. matlab算出中心差分方法_方差分析、T检验、卡方分析如何区分?
  6. Percona-tookit学习笔记(一)
  7. 直播丨探究Oracle多租户架构
  8. 计算机网络专业图片,计算机网络专业个人简历封面图片
  9. Firefox6 使用 firebug 解决方法 以及迅雷(thunder)插件报错
  10. 普洛斯库列科夫 线性代数习题集_转载)科大学长对数学系学弟学妹的忠告
  11. android 聊天动态表情包,gif表情包制作app下载-GIF表情包 安卓版v1.0-PC6安卓网
  12. 汽车方向盘转角传感器
  13. 降低电源纹波噪声的方法
  14. 什么是世界协调时间UTC
  15. Python——列表的方法
  16. 什么是FD.IO/VPP?
  17. 树莓派 人体感应器HC-SR501的应用
  18. week1:字符格式化输出,数据类型,for循环,break,continue,标志位
  19. LOL云顶开心果、阿米、夜猫
  20. Golang语言快速上手到综合实战(Go语言、Beego框架、高并发聊天室、豆瓣电影爬虫) 下载

热门文章

  1. 实战:使用Mask-RCNN的停车位检测
  2. HI3519V101支持的sensor
  3. Java如何执行操作系统的CMD命令行
  4. 十六、curator recipes之DistributedIdQueue
  5. 网易云易盾荣获雷锋网颁发的年度最佳视频内容审查奖
  6. mysql创建表语句 引擎、编码和字符集设置
  7. Android控件点击圆形阴影反馈解决方案
  8. ios 3DTouch初识
  9. gitlab+jenkins+maven+docker持续集成(四)——Extended E-mail Notification配置
  10. 邮件客户端WebMail Pro v7.7.5发布,在线订购限时75折优惠!