递推与递归

本文介绍了递推和递归的概念及其在解决实际问题中的应用,例如通过递推分析解决25盏灯的游戏,探讨约数之和的计算方法,以及利用递归构建分形城市的过程。通过这些实例,展示了如何从已知状态推导未知状态,以及在数据结构和算法设计中如何运用递归思想。
摘要由CSDN通过智能技术生成

这篇文章重在介绍递归和递推的思想,并以几道例题的分析来体会体会。

递推

  1. 在怎样的条件下,我们才能用递推呢?
    (1)递推是从已知条件开始的,一步一步地推过去, 必须有明确的通用公式。
    (2)必须是有限次运算。

  2. 特点:通过前面的一些项来得出序列中的指定象的值。

递归

  1. 特点:
    (1) 数据之间的逻辑关系是递归的,如树、图等的定义和操作。
    (2)函数中有直接或间接调用自身的语句
    (3)在使用递归策略时,必须有一个明确的递归结束条件,称之为递归出口(或递归边界)

其实,我们不必太过于纠结于递归与递推的区别,两者思想相差不大,从已知到未知。下面几道题体会一下递推与递归的思想吧。

费解的开关

题目链接:戳我
题目描述:
25 盏灯排成一个 5×5 的方形。
每一个灯都有一个开关,游戏者可以改变它的状态。
每一步,游戏者可以改变某一个灯的状态。
游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字 1 表示一盏开着的灯,用数字 0 表示关着的灯。
在这里插入图片描述
在这里插入图片描述

分析:

  1. 每个灯最多按一次;且按的顺序无所谓(谁先谁后没影响)。
  2. 从上往下,推出每个灯该按不该按。:
    【假设第一行的开关已经不能按了,当第一行某列是1时(亮着的状态)那第二行的该列就不能按了;反之,若第一行某列的状态是0(暗着的状态),那第二行的该列一定要按一按的。同理,第三、四行亦是如此,当第五行把第四行的灯都变亮时,判断第五行是不是全亮,即可判断该方案是否合法】
    发现:当第一行的灯状态确定,则后边所有开关的状态都可以被推出来, 只需要检查最后一行最后是否含0即可
  3. 枚举第一行的所有操作,对于每个操作方案,推出来后面每行的方案,判断是否合法。记录最小值。
  4. 可以用二进制的一个应用,即每个数对应的二进制表示都是唯一的,每一个二进制表示中是1的表示按一下开关。如i = 6, 二进制表示:00110, 表示按一下第一个和第二个开关,for(i = 0; i < 32; i ++) 即可全部枚举出第一行的所有操作。关于位运算的一些相关知识,大家可以参考这篇博客戳我跳转
  5. 也可用高斯消元来做,
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 6;
char g[N][N], bg[N][N];
int dx[] = {-1, 0, 1, 0, 0}, dy[] = {0, 1, 0, -1, 0};
void turn(int x, int y){
    for(int i = 0; i < 5; i ++){
        int a = x + dx[i], b = y + dy[i];
        if(a < 0 || a >= 5 || b < 0 || b >= 5) continue;
        g[a][b] ^= 1;
    }
}

int main(void){
    int T;
    scanf("%d", &T);
    while(T --){
        for(int i = 0; i < 5; i ++) scanf("%s", bg[i]);
        int res = 10; //本题大于6便可输出-1,只要res初始值>6即可
        //操作第一行的开关
        for(int op = 0; op < 32; op ++){
            int cnt = 0;
            memcpy(g, bg, sizeof g);
            for(int i = 0; i < 5; i ++){
                if(op >> i & 1){
                    turn(0, i);
                    cnt ++;
                }
            }
            //递推出1~4行开关状态
            for(int i = 0; i < 4; i ++){
                for(int j = 0; j < 5; j ++){
                    if(g[i][j] == '0'){
                        turn(i + 1, j);
                        cnt ++;
                    }
                }
            }
            bool flag = true;
            //检查最后一行灯是否全亮
            for(int i = 0; i < 5; i ++){
                if(g[4][i] == '0') flag = false;
            }
            if(flag && res > cnt) res = cnt;
        }
        if(res > 6) res = -1;
        printf("%d\n", res);
    }
    return 0;
}

约数之和

戳我:链接

题目描述:
在这里插入图片描述
约数公式:
在这里插入图片描述
这里有一篇来专门阐述约数之和戳我跳转

分析:

  1. 那我们的重点就在如何求p ^0 + p ^ 1 + ... + p ^k = sum(p, k) 那思考如何将 sum(p,k)转化成一个个小问题。
  2. 当k为奇数时,0-k便为偶数个个数,我们可以以k/2为界,把所求对应sum值分成两部分。
    在这里插入图片描述
    当k为偶数时,共k+1项
    在这里插入图片描述
#include <cstdio>
#include <cstring>
using namespace std;
const int mod = 9901;
int qmi(int a, int b){
    int res = 1;
    a = a % mod;
    while(b){
        if(b & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}

int sum(int p, int k){
    if(k == 0) return 1;
    if(k % 2 == 0) return (p % mod * sum(p, k - 1) + 1) % mod;
    return (1 + qmi(p, k / 2 + 1)) * sum(p, k / 2) % mod;
}

int main(void){
    int A, B;
    scanf("%d%d", &A, &B);
    int res = 1;
//得出i是不是质因数,以及得出i的幂次
    for(int i = 2; i <= A; i ++){
        int s = 0;
        while(A % i == 0){
            s ++;
            A /= i;
        }
        if(s) res = res * sum(i, s * B) % mod;
    }
//如果A为0,那么res = 0.
    if(!A) res = 0;
    printf("%d\n", res);
    return 0;
}

分形之城

题目链接:戳我

题目描述:
在这里插入图片描述
在这里插入图片描述

分析:

  1. 第一个图形大小2^1 * 2 ^ 1, 第二个大小:2 ^ 2 * 2 ^ 2, 第三个大小:2 ^ 3 * 2 ^ 3;
    像我们第三个:这8*8的图形,可平分成四部分,每部分可以看成一个2*2变化而来的。
  2. 如图所示,对于某个平分成四部分的图形来说,左边均是需要按照图示坐标系的设置对称变换而来的。
    在这里插入图片描述
    我们把这个图形放置到数组中,我们输入N, A,B后,我们可以找到(N,A)的位置,(在N*N的中,对应数字为A的坐标), 然后找到(N,B)的位置,不难发现,N*N的矩阵中,一共有2 ^ N * 2 ^N个数,我们分成的四份中,每份中有X = 2^(n-1) * 2 ^ (n-1)个数, 我们用A整除X, 若为0,则为左上角的那个小矩阵,若为1则为右上角的小矩阵,若为3则为左下角的小矩阵,若为2则为右下角的小矩阵。此时所在子矩阵的维度设为block,则与N-1的矩阵编号还差一个偏移量,我们可以%block, 变成对应小矩阵的编号(从1开始的那种)在子矩阵中(子矩阵以0,0为坐标原点)(x, y)=get(N-1, A % block)【实际上这一步已经将大问题转化成一个子问题的求解】,对应到原大矩阵中(向右向下的平移,如有对称啥的需要变换,则做相应的变换),

(1)商为0时:该区域的点应该做关于y=x的对称,即x,y换一下位置。(注意观察上图中y = x具体所指的是哪条线,跟我们数学上的坐标有所不同)(y, x)

(2)商为1时:(x, y + 2^(n-1))

(3)商为2时,(x + 2 ^ (n-1), y + 2 ^ (n-1))
(4)商为3时,我们需要做对称变换,如图所示,对称变换后,还需要移动。
在这里插入图片描述
图中有一个小问题,因为我们的坐标是从0开始计算的,所以xa' = 2 ^(n-1) - 1 - y0 ya' = 2 ^ (n - 1) - 1 - x0
变换完成后,对应在大矩阵中的坐标2 ^(n-1) + 2 ^ (n-1) - 1 - y, 2 ^(n-1) - 1- x

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;

struct Point{
    LL x, y;
};
Point get(LL n, LL a){
    if(n == 0) return {0, 0};
    LL block = 1ll << (n * 2 - 2),len = 1ll << (n - 1);
    auto p = get(n - 1, a % block);
    LL x = p.x, y = p.y;
    int z = a / block;
    if(z == 0) return {y, x};
    else if(z == 1) return {x, y + len};
    else if(z == 2) return {x + len, y + len};
    return {len * 2 - 1 - y, len - 1 - x};
}

int main(void){
    int T;
    scanf("%d", &T);
    while(T --){
        LL n, a, b;
        cin >> n >> a >> b;
//我们第一个数以0开始,所以传入的参数是a-1, b-1;
        auto pa = get(n, a - 1);
        auto pb = get(n, b - 1);
        double dx = pa.x - pb.x, dy = pa.y - pb.y;
        printf("%.0lf\n", sqrt(dx * dx + dy * dy) * 10);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Xuhx&

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

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

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

打赏作者

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

抵扣说明:

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

余额充值