这篇文章重在介绍递归和递推的思想,并以几道例题的分析来体会体会。
递推
-
在怎样的条件下,我们才能用递推呢?
(1)递推是从已知条件开始的,一步一步地推过去, 必须有明确的通用公式。
(2)必须是有限次运算。 -
特点:通过前面的一些项来得出序列中的指定象的值。
递归
- 特点:
(1) 数据之间的逻辑关系是递归的,如树、图等的定义和操作。
(2)函数中有直接或间接调用自身的语句
(3)在使用递归策略时,必须有一个明确的递归结束条件,称之为递归出口(或递归边界)
其实,我们不必太过于纠结于递归与递推的区别,两者思想相差不大,从已知到未知。下面几道题体会一下递推与递归的思想吧。
费解的开关
题目链接:戳我
题目描述:
25 盏灯排成一个 5×5 的方形。
每一个灯都有一个开关,游戏者可以改变它的状态。
每一步,游戏者可以改变某一个灯的状态。
游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。
我们用数字 1 表示一盏开着的灯,用数字 0 表示关着的灯。
分析:
- 每个灯最多按一次;且按的顺序无所谓(谁先谁后没影响)。
- 从上往下,推出每个灯该按不该按。:
【假设第一行的开关已经不能按了,当第一行某列是1时(亮着的状态)那第二行的该列就不能按了;反之,若第一行某列的状态是0(暗着的状态),那第二行的该列一定要按一按的。同理,第三、四行亦是如此,当第五行把第四行的灯都变亮时,判断第五行是不是全亮,即可判断该方案是否合法】
发现:当第一行的灯状态确定,则后边所有开关的状态都可以被推出来, 只需要检查最后一行最后是否含0即可
- 枚举第一行的所有操作,对于每个操作方案,推出来后面每行的方案,判断是否合法。记录最小值。
- 可以用二进制的一个应用,即每个数对应的二进制表示都是唯一的,每一个二进制表示中是1的表示按一下开关。如i = 6, 二进制表示:00110, 表示按一下第一个和第二个开关,
for(i = 0; i < 32; i ++)
即可全部枚举出第一行的所有操作。关于位运算的一些相关知识,大家可以参考这篇博客戳我跳转 - 也可用高斯消元来做,
#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;
}
约数之和
题目描述:
约数公式:
这里有一篇来专门阐述约数之和戳我跳转
分析:
- 那我们的重点就在如何求
p ^0 + p ^ 1 + ... + p ^k = sum(p, k)
那思考如何将 sum(p,k)转化成一个个小问题。 - 当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;
}
分形之城
题目描述:
分析:
- 第一个图形大小
2^1 * 2 ^ 1
, 第二个大小:2 ^ 2 * 2 ^ 2
, 第三个大小:2 ^ 3 * 2 ^ 3
;
像我们第三个:这8*
8的图形,可平分成四部分,每部分可以看成一个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;
}