第一讲 递归和递推

这篇博客介绍了如何使用递归实现指数型枚举和排列型枚举,以及简单斐波那契数列的计算。通过C++代码展示了递归在解决这些问题上的应用,包括从1到n的所有选择方案、数字的全排列以及斐波那契数列的前n项生成。此外,还提到了一个涉及灯泡状态变化的逻辑问题,探讨了如何找到最少步骤使所有灯泡变亮。
摘要由CSDN通过智能技术生成

目录

AcWing 92. 递归实现指数型枚举

AcWing 94. 递归实现排列型枚举

AcWing 717. 简单斐波那契

AcWing 95. 费解的开关


AcWing 92. 递归实现指数型枚举

从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式

输入一个整数 n。

输出格式

每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围

1≤n≤15

输入样例:

3

输出样例:


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

AcWing 92. 递归实现指数型枚举

#include<iostream>

using namespace std;

int n;

void dfs(int u,int state){
    //如果枚举到第n个数
    if(u==n){
        //就输出前面枚举的结果,利用state来确定第u个数是否被选过
        for(int i = 0;i<n;i++){
            //将state第i位右移,然后判断是0还是1,如果是1就输出
            if(state>>i & 1){
                //输出的数是从1开始枚举,而我们的计算是从0开始
                cout <<i+1<<" ";
            }
        }
        //输出空格
        cout<<endl;
        return ;
    }
    //不选下一个数
    dfs(u+1,state);
    //选下一个数,并在该位标记
    dfs(u+1, state|1<<u);
}

int main(){
    //输入需要枚举的数的范围,n最大为15,最多情况3万多
    //可以用int 4字节的后15个比特位来表示所有的状态
    cin>>n;
    //数从0开始枚举,第二个参数为0,说明初始化所有的比特位为0
    dfs(0,0);
   
    return 0;
}

state>>i & 1 先将第i位移到第1位然后判断它是0还是1

state|1<<u 将0变成1然后移到第u位

94. 递归实现排列型枚举

把 1∼n 这 n 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式

一个整数 n。

输出格式

按照从小到大的顺序输出所有方案,每行 1 个。 

首先,同一行相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

数据范围

1≤n≤9

输入样例:

3

输出样例:

1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include<iostream>
#include<vector>

using namespace std;

int n;

//相当于c语言里面的一维数组
vector<int> path;

void dfs(int u,int state){
    if(u==n){
        //和java里面的增强for循环一样。到了南墙,输出所有的数。
        for(auto i:path)cout<<i<<" ";
        cout<<endl;
        return;
    }
    for(int i=0;i<n;i++){
        //判断第i个数是否放在过坑里
        //先把第i个位比特位移到第一位然后看他是0是1
        //表示第i个数是否被使用过
        if(!(state>>i&1)){
            //如果没有使用过就入坑
            path.push_back(i+1);
            //搜索下一个数,然后把state的第i位标记为1,表示第i个数已经被使用过了
            dfs(u+1,state|1<<i);
            //回溯,撞到南墙该回头了。
            path.pop_back();
        }
    }
}

int main(){
    cin>>n;
    //param1:从0开始搜索到n-1
    //param2:二进制数表示哪些数被用过了,这里表示第一个数没有被用过开始搜索。
    dfs(0,0);
    return 0;
}

717. 简单斐波那契

以下数列 0 1 1 2 3 5 8 13 21 ... 被称为斐波纳契数列。 

这个数列从第 3 项开始,每一项都等于前两项之和。

输入一个整数 N,请你输出这个序列的前 N 项。

输入格式

一个整数 N。

输出格式

在一行中输出斐波那契数列的前 N 项,数字之间用空格隔开。

数据范围

0<N<46

输入样例:

5

输出样例:

0 1 1 2 3

递推做法

#include<iostream>

using namespace std;

int main(){
    int n;
    //数据范围
    int a[46];
    cin>>n;
    //初始条件
    a[0] = 0;
    a[1] = 1;
    //递推
    for(int i=2;i<n;i++){
        //递推公式
        a[i] = a[i-1]+a[i-2];
    }
    for(int i=0;i<n;i++)cout<<a[i]<<" ";
    return 0;
}

优化:滚动数组的雏形,节省空间

#include<iostream>

using namespace std;

int main(){
    int n;
    cin>>n;
    //初始条件
    int a = 0,b = 1;
    //递推
    for(int i=0;i<n;i++){
        //递推公式
        cout<< a <<" ";
        //a 和 b 同时往后错位
        //存好第三个数
        //让a等于第二个数
        //让b等于第三个数
        int fn = a+b;
        a = b;
        b = fn;
    }
    
    return 0;
}

AcWing 95. 费解的开关

你玩过“拉灯”游戏吗?

25 盏灯排成一个 5×5 的方形。

每一个灯都有一个开关,游戏者可以改变它的状态。

每一步,游戏者可以改变某一个灯的状态。

游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字 1 表示一盏开着的灯,用数字 0 表示关着的灯。

下面这种状态

10111
01101
10111
10000
11011

在改变了最左上角的灯的状态后将变成:

01111
11101
10111
10000
11011

再改变它正中间的灯后状态将变成:

01111
11001
11001
10100
11011

给定一些游戏的初始状态,编写程序判断游戏者是否可能在 6 步以内使所有的灯都变亮。

输入格式

第一行输入正整数 n,代表数据中共有 n 个待解决的游戏初始状态。

以下若干行数据分为 n 组,每组数据有 5 行,每行 5 个字符。

每组数据描述了一个游戏的初始状态。

各组数据间用一个空行分隔。

输出格式

一共输出 n 行数据,每行有一个小于等于 6 的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。

对于某一个游戏初始状态,若 6 步以内无法使所有灯变亮,则输出 −1。

数据范围

0<n≤500

输入样例:

3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

输出样例:

3
2
-1
#include<iostream>
#include<cstring>

using namespace std;
//先把大体框架搭出来,然后再实现细节的函数
//取名最好见名知意,debug的时间往往要多于写代码

int const INF = 1000000;

char g[10][10];

//这两个一维数组表示本身和周围共5个位置
int dx[5] = {0,-1,0,1,0} , dy[5] = {0,0,1,0,-1};

//功能:将某个下标和周围的数取反
void turn(int x,int y){
    int a,b;
    //遍历周围的五个方向,分别取反
    for(int i = 0;i<5;i++){
        //a和b为偏移后位置的下标
        a = x + dx[i];
        b = y + dy[i];
        //如果偏移后的位置没有越界就取反
        if(a>=0&&a<5&&b>=0&&b<5){
            g[a][b] ^= 1;
        }
    }
}

int work(){
    //这个数组用来做备份,来存储g的原来状态
    char backup[10][10];
    //等于一个较大的数
    int ans = INF;
    //将g复制到backup里,cstring头文件中
    memcpy(backup, g, sizeof g);
    //遍历第一行的全部32种情况,对第一行进行的操作,决定了后面几行的处理情况。
    //只有将第一行按压灯泡的所有可能情况都取到,才能补充不漏的找到最少的操作数。
    for(int k = 0;k<1<<5;k++){
        //开始一种新的情况
        int res = 0;
        //如果有1就按压,这里的1不代表灯泡的状态,而是代表是否按压灯泡
        //第i位是否是1,如果是1就进行按压,操作数增加
        for(int i = 0;i<5;i++){
            if(k>>i&1){
                res++;
                turn(0, i);
            }
        }
        //上面是对第一行进行操作后的初始状态,接下来除最后一行,每一行都跟着进行处理
        //如果某个位置是0,就把这个位置下方的数按压,让这个位置的数变成1
        for(int i = 0;i<4;i++){
            for(int j = 0;j<5;j++){
                if(g[i][j]=='0'){
                    res++;
                    turn(i+1, j);
                }
            }
        }
        
        //利用最后一行判断是否处理成功,也就是所有灯泡都是亮的
        bool is_successful = true;
        for(int i = 0;i<5;i++){
            if(g[4][i]!='1'){
                is_successful = false;
                break;
            }
        }
        
        //获得32种情况中,满足条件的最小的数
        if(is_successful) ans = min(ans,res);
        
        //一种情况计算完毕,将g重新复原,进行下一个状态的计算
        memcpy(g, backup, sizeof g);
    }
    
    //题目要求,如果操作数大于6输出-1
    if(ans>6) ans = -1;
    return ans;
}

int main(){
    
    int T;
    cin>>T;
    while(T--){
        //因为是二维的字符数组,所以可以一行行输入
        for(int i = 0;i<5;i++) cin>>g[i];
        
        cout<<work()<<endl;
    }
    
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小蒋的学习笔记

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

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

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

打赏作者

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

抵扣说明:

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

余额充值