当前位置:   article > 正文

Day1递归与递推--蓝桥杯学习笔记_蓝桥杯最优包含问题的时间复杂度

蓝桥杯最优包含问题的时间复杂度

目录

递推与递归的简单应用

例题:递归实现指数型枚举

例题:递归实现排列型枚举 

Day1练习题

蓝桥杯2013年第四届真题--带分数 

蓝桥杯往届试题:翻硬币

车厢重组(冒泡排序)

蓝桥杯2014年第五届真题:波动数列

母牛的故事

明明的随机数


递推:以已知的“问题边界”为起点向“原问题”正向推导的扩展方式就是递推。

递归:然而在很多时候,推导的路线难以确定,这时以“原问题”为起点尝试寻找把状态空间缩小到已知的“问题边界”的路线,再通过该路线反向回溯的遍历方式就是递归

自身调用自身、回溯时还原现场,如下图所示:

下面两幅图来表示递推与递归的差别:

递推与递归的简单应用

在使用枚举算法蛮力探索问题的整个“状态空间”时,经常需要递归。按照规模大小,有如下几种常见的美剧形式和遍历方式:

例题:递归实现指数型枚举

 等价于每个整数可以选或者不选,所有可能的方案总数共有2^{n}

  1. #include<stdio.h>
  2. int n,num,ans[10000];
  3. void dfs(int dep){
  4. if(dep>n){
  5. for(int i=1;i<=num;i++){
  6. printf("%d ",ans[i]);
  7. }
  8. printf("\n");
  9. return;
  10. }
  11. //选
  12. ans[++num]=dep;
  13. dfs(dep+1); //dep表示当前考虑的那个数
  14. //不选
  15. num--;
  16. dfs(dep+1);
  17. }
  18. int main(void){
  19. scanf("%d",&n);
  20. dfs(1);
  21. return 0;
  22. }
  1. #include<iostream>
  2. #include<vector>
  3. using namespace std;
  4. int n;
  5. vector<int> v; //动态长度数组(int型)
  6. void dfs(int dep){
  7. if(dep>n){
  8. //print ans
  9. for(int i=0;i<v.size();i++){
  10. cout<<v[i]<<' ';
  11. }
  12. cout<<endl;
  13. return;
  14. }
  15. //选择它
  16. //将该数放入答案序列
  17. //dfs(dep+1);
  18. dfs(dep+1);
  19. v.push_back(dep);
  20. //不选它
  21. //确保该数不在答案序列
  22. dfs(dep+1);
  23. v.pop_back();
  24. }
  25. int main(void){
  26. cin>>n;
  27. dfs(1);
  28. return 0;
  29. }

例题:递归实现排列型枚举 

  1. #include<iostream>
  2. using namespace std;
  3. int n,chosen[10],order[10],dep;
  4. bool f[10];
  5. void dfs(int dep){
  6. //检查边界;如果得到答案,就输出答案
  7. //如果说到了边界,还没得到答案,return
  8. if(dep>n){
  9. for(int i=1;i<=n;i++)
  10. cout<< order[i]<<' ';
  11. cout<<endl;
  12. return ;
  13. }
  14. //遍历当前可以选择的数字
  15. for(int i=1;i<=n;i++){
  16. //如果一个数不可以用
  17. if(chosen[i])
  18. continue;
  19. //如果一个数可以用
  20. //标记为已用
  21. order[dep]=i;
  22. chosen[i]=true;
  23. dfs(dep+1);
  24. //标记成未用
  25. chosen[i]=0;
  26. order[dep]=0;
  27. }
  28. }
  29. int main(void)
  30. {
  31. cin>>n;
  32. dfs(1);
  33. return 0;
  34. }

Day1练习题

蓝桥杯2013年第四届真题--带分数 

问题分析:

①递归解决排列问题

n=a+b/c
找1~9的排列     362880
在这个排列中加一个+再加一个/     64
计算符合条件的数量   2e7

时间复杂度过高会超时,蓝桥杯竞赛中允许的最高时间复杂度为1e8

递归排列组合

n=a+b/c
n*c=a*c+b
b=c*n-a*c    只需要遍历两个未知数a和c即可确定未知数b


·枚举a : 1~9里面先选数(递归解决指数型问题),再在选择的数中排列
·枚举c :在剩下的数中选数,再在选择的数中排列
·确定b :通过表达式b=c*n-a*c求值
·check :
    看b是否合法(b当中的数字,是否包含了a和c选剩下的,而且没有和a,c重复的数字
    看等式是否成立

做题技巧:
1.排列123->整数123
    1 -> 1*10+2 -> 12*10+3 
2.整数123->排列123(检查b是否合法)

  1.     int num;
  2.     while(num){
  3.         int n=num%10;
  4.         num/=10;
  5.     }

思路一 AC代码1:

  1. #include<cstdio>
  2. #include<iostream>
  3. #include<algorithm>
  4. using namespace std;
  5. int main(void){
  6. int a[]={1,2,3,4,5,6,7,8,9};
  7. int n,num=0;
  8. cin>>n;
  9. do{
  10. int num1,num2,num3;
  11. for(int i=1;i<8;i++){
  12. for(int j=i;j<8;j++){
  13. num1=num2=num3=0;
  14. for(int k=0;k<i;k++){
  15. num1=num1*10+a[k];
  16. }
  17. for(int k=i;k<=j;k++){
  18. num2=num2*10+a[k];
  19. }
  20. for(int k=j+1;k<=8;k++){
  21. num3=num3*10+a[k];
  22. }
  23. if(num2%num3==0){
  24. if(num2/num3+num1==n){
  25. num++;
  26. }
  27. }
  28. }
  29. }
  30. }while(next_permutation(a,a+9));
  31. cout<<num<<endl;
  32. return 0;
  33. }

思路一 AC代码2:

  1. #include<cstdio>
  2. #include<algorithm>
  3. using namespace std;
  4. int num[9] = {1,2,3,4,5,6,7,8,9};
  5. //分别代表 a b c
  6. int a ,b ,c ,n;
  7. //获取在排列中l位置到r位置所得到的数字
  8. int getNum(int l,int r){
  9. int sum = 0;
  10. for(int i=l ;i<=r ;i++){
  11. sum = sum*10+num[i];
  12. }
  13. return sum;
  14. }
  15. int main(){
  16. int ans = 0;
  17. scanf("%d",&n);
  18. do{
  19. //枚举a
  20. for(int i=0 ;i<6 ;i++){
  21. a = getNum(0,i);
  22. if(a>n)break;
  23. if(a<n){
  24. //枚举b,b的位置可以从a的下一位开始枚举,
  25. //或者直接从剩余的数字的一半开始(减少枚举量)
  26. for(int j=i+1 ;j<8 ;j++){
  27. b = getNum(i+1,j);
  28. c = getNum(j+1,8);
  29. if(b>=c&&b%c==0&&(b/c+a)==n){
  30. ans++;
  31. }
  32. }
  33. }
  34. }
  35. }while(next_permutation(num,num+9));
  36. printf("%d\n",ans);
  37. return 0;
  38. }

思路二 AC代码:

  1. #include<iostream>
  2. using namespace std;
  3. int main(){
  4. }

蓝桥杯往届试题:翻硬币

已知初始状态和要达到的目标状态,每次只能同时翻转两个相邻的硬币
只有偶数个不一样的时候才能达到目标状态
只要存在不一样的位置就必须翻
递推:一直往前走,只增不减

  1. #include<iostream>
  2. #include<cstring>
  3. using namespace std;
  4. string s1,s2;
  5. int ans;
  6. void fan(int i){
  7. if(s1[i]=='*') s1[i]='o';
  8. else if(s1[i]=='o') s1[i]='*';
  9. }
  10. int main(void){
  11. cin>>s1>>s2;
  12. //索引到倒数第二个,因为必须同时翻两个硬币
  13. for(int i=0;i<s1.size()-1;i++){
  14. if(s1[i]!=s2[i]){
  15. fan(i);
  16. fan(i+1);
  17. ans++;
  18. }
  19. }
  20. cout<<ans<<endl;
  21. return 0;
  22. }

车厢重组(冒泡排序

  1. #include<iostream>
  2. using namespace std;
  3. int N,a[10000],num=0,tem;
  4. int main(void){
  5. cin>>N;
  6. for(int i=0;i<N;i++){
  7. cin>>a[i];
  8. }
  9. for(int i=1;i<N;i++){
  10. for(int j=0;j<N-i;j++){
  11. if(a[j]>a[j+1]){
  12. tem=a[j];
  13. a[j]=a[j+1];
  14. a[j+1]=tem;
  15. num++;
  16. }
  17. }
  18. }
  19. cout<<num<<endl;
  20. return 0;
  21. }

蓝桥杯2014年第五届真题:波动数列

  1. #include<iostream>
  2. #define LL long long
  3. #define MOD 100000007
  4. using namespace std;
  5. int main()
  6. {
  7. LL n, s, a, b,sum_a,sum_b;
  8. LL dp[1010][1010] ;//dp[i][j] 代表序列前Ki的和取模n后的值为 j的方案数
  9. cin>>n>>s>>a>>b;
  10. dp[1][(s%n+n)%n] = 1;//相当于式子的s%n,即 K1 ( (s%n+n) 是处理负数)
  11. for (int i = 2; i<=n; i++){// 式子的前Ki项
  12. sum_a = a*(n-i+1)%n;//假定 第i项为加上 a 那么 a 的贡献为 a*(n-i+1)%n
  13. sum_b = b*(n-i+1)%n;//假定 第i项为减上 b 那么 b 的贡献为 b*(n-i+1)%n
  14. for (int j = 0; j < n; j++)//遍历所有前K(i-1)对n取模可能取到的值的方案数
  15. {
  16. dp[i][(j-sum_a+n)%n] = (dp[i][(j-sum_a+n)%n] + dp[i-1][j]) % MOD;
  17. //前Ki项为j-sum_a对n取模的方案数 等于
  18. // 其它对n取模也得 j-sum_a值 加上 前K(i-1)项取模n的值j (j-sum_a+n)防止负数
  19. dp[i][(j+sum_b)%n] = (dp[i][(j+sum_b)%n] + dp[i-1][j]) % MOD;
  20. //前Ki项为j+sum_b对n取模的方案数 等于
  21. // 其它对n取模也得 j+sum_b值 加上 前K(i-1)项取模n的值j
  22. }
  23. }
  24. cout<<dp[n][0]<<endl;//输出前Kn项对n取模后为0的方案数 即是答案。
  25. return 0;
  26. }

母牛的故事

  1. #include <iostream>
  2. using namespace std;
  3. int a[60];//开的稍微大一点
  4. int main()
  5. {
  6. int n;
  7. a[1] = 1;
  8. a[2] = 2;
  9. a[3] = 3;
  10. a[4] = 4;
  11. for(int i=5;i <= 60;i++)
  12. a[i] = a[i-1] + a[i-3];
  13. while(cin>>n && n != 0){
  14. cout<<a[n]<<endl;
  15. }
  16. return 0;
  17. }

明明的随机数

  1. #include<iostream>
  2. using namespace std;
  3. int a[10000],n,N,num=0;
  4. int main(void){
  5. cin>>N;
  6. for(int i=1;i<=N;i++){
  7. cin>>n;
  8. a[n]=1;
  9. }
  10. for(int i=0;i<=1000;i++){
  11. if(a[i]==1) num++;
  12. }
  13. cout<<num<<endl;
  14. for(int i=0;i<=1000;i++){
  15. if(a[i]==1) cout<<i<<' ';
  16. }
  17. }
  1. #include<iostream>
  2. #include<set>
  3. using namespace std;
  4. int main()
  5. {
  6. int tem;
  7. set<int> a;
  8. int n;
  9. cin>>n;
  10. for(int i=1;i<=n;i++)
  11. {
  12. cin>>tem;
  13. a.insert(tem); //自动跳过已经存在的数 且插入时自动排序
  14. }
  15. cout<<a.size()<<endl;
  16. set<int>::iterator it; //set<int>::iterator迭代器 it:集合中某一个数的地址
  17. //迭代器:定义了一个it迭代器用来访问集合某个元素的地址
  18. for(it=a.begin();it!=a.end();it++)
  19. //a.begin()集合第一个数的地址
  20. //a.end()集合最后一个数的下一个地址
  21. cout<<*it<<' ';
  22. return 0;
  23. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/1013929
推荐阅读
相关标签
  

闽ICP备14008679号