前言
今天的主题是递归,主播给大家带来了递归的两道经典问题和一道比较偏向数学的题目。
带分数
分析
主播现在才发现其实以前做的很多“水题”并不是真正的“水题”,就比如这道题,要让主播写一个组合数主播肯定是能写出来的。这道题也很明显和组合数有关,但是主播最开始却并没有从组合数的角度去看待这道题,而是写的暴力,没有丝毫悬念超时了……
后来看了题解才发现大家都是先求组合数,随后在枚举划分区间求解的。主播认为这就是技巧,分析题目的技巧。
题目中要求1 ~ 9
分别出现且只出现一次,这其实就是1 ~ 9
的组合数。组合数的代码我给大家贴下来了,其实就是回溯的应用。
int dfs() //分类讨论放在哪里
{for(int i = 1; i <= 9; i++)if(!read[i]){read[i] = true;dfs();read[i] = false;}
}
所有我们枚举组合数,随后用隔板法枚举每个区间的长度。
注意这里有一个小细节,按照题目的意思每一个部分都是没有0的,这其实也就是说每一部分都不是0。
代码
#include<iostream>
#include<vector>
using namespace std;
const int N = 10;
int n;bool read[N]; //每个位置有没有用
vector<int> vtr;int toInt(int l, int r)
{int cnt = 0;for(int i = l; i <= r; i++){cnt *= 10;cnt += vtr[i];}return cnt;
}int dfs() //分类讨论放在哪里
{int l = 0;if(vtr.size() == 9) //选完了{for(int i = 0; i < 8; i++)for(int j = i + 1; j < 8; j++){int x = toInt(0, i);int y = toInt(i + 1, j);int z = toInt(j + 1, 8);if(x * z + y == n * z) l++;}}else{for(int i = 1; i <= 9; i++){if(!read[i]){read[i] = true;vtr.push_back(i);l += dfs();read[i] = false;vtr.pop_back();}}}return l;
}int main()
{scanf("%d", &n);printf("%d", dfs());return 0;
}
正则问题
分析
如果是工程上的话肯定就去写数字栈和符号栈和优先级表了,主包最开始也是这样想的,但是这是一道算法题,算法追求的是什么,快准狠啊。
所以主播毅然决然的放弃了栈写法,选择了递归……
递归写法和栈写法略有不同,原因在于递归更加的灵活,可以跳过栈的一些写法表示出来。
代码我就直接贴出来了,主播觉得比较难想的地方就是将字符串的加法分配给左括号,这应该也算是一种经验吧,主播最开始是想着将拼接供给右括号的,但是发现最终没有办法实现,至于为什么要分配给左括号是因为左括号可以更方便的处理拼接(主播认为其实左括号和右括号都可以,因为左括号方便处理而选择了左括号)
代码的思路是将括号和符号作为分界点,数字作为基本元素。
代码
/*中缀表达式运算
*/
#include<iostream>
#include<stack>
using namespace std;
const int N = 110;
char s[N];
int cnt = 0;
int dfs(char s[], int &cnt)
{int res = 0;while(s[cnt]){if(!s[cnt]) return 0;if(s[cnt] == '('){res += dfs(s, ++cnt);cnt++;}else if(s[cnt] == ')')break;else if(s[cnt] == '|')res = max(res, dfs(s, ++cnt));else{cnt++;res++;}}return res;
}int main()
{scanf("%s", s);printf("%d", dfs(s, cnt));return 0;
}
有序分数
分析
这道题暴力的话是很好想的,我们枚举分子和分母,随后将分子分母互质的数字存起来,最后排序输出就可以了。
不过这不符合今天的主题,用递归怎么写呢?最开始主播百思不得其解,随后看了题解后才发现——这不是一道数学题吗?
我们是借助一种结构来解题的——[Stern-Brocot Tree]
。
理论上可以计算出区间内所有的有理数,不过我们知道这基本上是不可能的,所以是理论上。原理是什么呢?
我们举个例子,比如[0, 1]
的这个区间,表示成分数。
[0/1, 1/1]
随后分子加分子,分母加分母得到1/2
,最后再用这个数划分区间,用分治去遍历左右两边。
[0/1, 1/2] [1/2, 1/1]
随后一直重复此操作进行下去,每次枚举的中间点就是区间内的一个有理数。
可能有的小伙伴发现了这不是一颗完全二叉树吗?既然是树,我们若想按照顺序遍历就要使用中序遍历。
对于本题来说我们限制一下分母的范围即可。
代码
// SBtree,一种可以遍历区间内所有有理数的算法
#include<iostream>
using namespace std;
int n;void dfs(int x1, int y1, int x2, int y2)
{if(y1 + y2 > n) return; dfs(x1, y1, x1 + x2, y1 + y2);printf("%d/%d\n", x1 + x2, y1 + y2);dfs(x1 + x2, y1 + y2, x2, y2);
}
int main()
{scanf("%d", &n);printf("%d/%d\n", 0, 1);dfs(0, 1, 1, 1);printf("%d/%d\n", 1, 1);return 0;
}