【基础算法】递推与递归

题目总结

0x01:约数之和

【题目】
假设现在有两个自然数 A 和 B,S 是 AB 的所有约数之和。
请你求出 Smod9901 的值是多少。

【输入格式】
在一行中输入用空格隔开的两个整数 A 和 B。
【输出格式】
输出一个整数,代表 Smod9901 的值。

【数据范围】

0≤A,B≤5×107

【输入样例】

2 3

【输出样例】

15

注意: A 和 B 不会同时为 0。
【解题思路】

引进概念:算数基本定理(唯一分解定理):对于任何一个合数,我们都可以用几个质数的幂的乘积来表示。
即:num = p1k1 *p2k2 * … * pnkn ,p1 < p2 < … <pn
例:72 = 23 * 32

根据以上公式可得
numq = p1k1*q *p2k2*q * … * pnkn*q

接着可以看出p1 有 k1 + 1中选择可能, p2 有k2 + 1中可能…

约数和 = (p10 * p11 *… * p1k1) * (p20 * p21 *… * p2k1) * … * (pn0 * pn1 *… * pnk1)
证明:拆开后,可以发现每三项组合起来就是约数

所以我们找出 A 的约数,接着找出他具有几次方,并计算sum(p, k) = (p10 * p11 *… * p1k) — 递归二分 O(logn)
需要分情况(从零开始):
奇数(p * sum(p, k - 1) + 1)
偶数 ((1 + qmi(p, k / 2 + 1)) * sum(p, k / 2) )

【CODE】

#include<iostream>

using namespace std;

const int MOD = 9901;

int qmi(int a, int k){
    a %= MOD;
    int res = 1;
    while(k){
        if(k & 1) res = res * a %MOD;
        a = a * a % MOD;
        k >>= 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) % MOD + 1) % MOD;
    return (1 + qmi(p, k / 2 + 1)) * sum(p, k / 2) % MOD;
}

int main(){
    int A, B;
    cin >> A >> B;
    int res = 1;
    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;
    }
    
    if(!A) res = 0;
    cout << res << endl;
    return 0;
}

0x02:分形之城

【题目】
城市的规划在城市建设中是个大问题。
不幸的是,很多城市在开始建设的时候并没有很好的规划,城市规模扩大之后规划不合理的问题就开始显现。
而这座名为 Fractal 的城市设想了这样的一个规划方案,如下图所示:
在这里插入图片描述
当城区规模扩大之后,Fractal 的解决方案是把和原来城区结构一样的区域按照图中的方式建设在城市周围,提升城市的等级。
对于任意等级的城市,我们把正方形街区从左上角开始按照道路标号。
虽然这个方案很烂,Fractal 规划部门的人员还是想知道,如果城市发展到了等级 N,编号为 A 和 B 的两个街区的直线距离是多少。
街区的距离指的是街区的中心点之间的距离,每个街区都是边长为 10 米的正方形。

【输入格式】
第一行输入正整数 n,表示测试数据的数目。
以下 n 行,输入 n 组测试数据,每组一行。
每组数据包括三个整数 N,A,B,表示城市等级以及两个街区的编号,整数之间用空格隔开。
【输出格式】
一共输出 n 行数据,每行对应一组测试数据的输出结果,结果四舍五入到整数。

【数据范围】
1≤N≤31,
1≤A,B≤22N,
1≤n≤1000

【输入样例】

3 
1 1 2 
2 16 1 
3 4 33 

【输出样例】

10 
30 
50 

【题意】
城市的大小和城市的等级有关,而且与前一级的城市的构造一样(就是上分成四个区域—左上,右上,右下,左上,并按这个顺序连接),给定两个序号,算出他们俩之间的直线距离。

【解题思路】
递归
在这里插入图片描述

可以看出等级 2 和等级 1 的关系:
第一部分是等级 1 进行顺时针旋转90度(移 1 为原点)再按x轴对称;
第二部分是等级 1 进行平移 一个部分的长度 (lenght);
第三部分是等级 1 分别向右和下平移length;
第四部分是等级 1 进行逆时针旋转90度再按y轴对称,接着向下移 2 * length - 1 ,接着向左移length - 1;

【CODE】

#include<iostream>
#include<cmath>

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

PII calc(LL n, LL m){
	if(!n) return 0;
	LL len = 1 << (n - 1); // 一个部分的长度
	LL cnt = 1ll << (2 * n - 2); // 一个部分的个数
	auto pos = calc(n - 1, m % cnt); // 递归下一层
	auto x = pos.first, y = pos.second;
	auto z = m / cnt; // 表示所处的位置,顺时针(0-3)
	if(z == 0) return {y, x};
	if(z == 1) return{x, y + len};
	if(z == 2) return {x + len, y + len};
	return {len * 2 - 1 - y, len - x - 1};
}

int main(){
	int T;
	cin >> T;
	while(T --){
		LL N, A, B;
		auto ac = calc(N, A - 1); // 下标从零开始
		auto bc = calc(N, B - 1);
		double x = ac.first - bc.first;
		double y = ac.second - bc.second;
		print("%.0lf\n", sqrt*(x * x + y * y) * 10); // 输出四舍五入的整数
	}
	return 0;
}

0x03:费解的开关

【题目】
你玩过“拉灯”游戏吗?
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

【解题思路】
我们判断(i, j)位置上是否为零,是的话,改变(i + 1, j)的灯关;这样在第 i 行就只会影响 第 j 列的灯,而不会影响其他的灯。
这样的话,我们假设第一行前面有一行,枚举其所有情况(1 << 5 种),再接着下面的判断。
【CODE】

#include<iostream>
#include<cstring> // memcpy

using namespace std;

char g[10][10];
int dx[5] = {0, -1, 0, 1, 0}, dy[5] = {0, 0, 1, 0, -1}; // 顺时针

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){
            g[a][b] = '0' + !(g[a][b] - '0');
            // g[a][b] ^= 1;
        }
    }
}

int work(){
    int ans = 0x3f;
    for(int k = 0; k < 1 << 5; ++k){ // 不确定第一行是否按不按,所以假设第一行上面也有一行,枚举所有情况
        int res = 0;
        char backup[10][10];
        memcpy(backup, g, sizeof g);

        for(int j = 0; j < 5; ++j){
            if(!(k >> j & 1)){ // 这里判断1和0都一样,但是注意不要在&后面把1变成0,这样会导致第一行永远不操作
                res++;
                turn(0, j);
            }
        }

        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 j = 0; j < 5; ++j){
            if(g[4][j] == '0'){
                is_successful = false;
                break;
            }
        }

        if(is_successful){
            ans = min(ans, res);
        }
        memcpy(g, backup, sizeof g);

    }
    if(ans > 6){
        return -1;
    }
    return ans;
}

int main(){
    int n;
    cin>> n;
    while( n --){
        for(int i = 0; i < 5; ++i){
            cin>> g[i];
        }
        cout << work() << endl;
    }
    return 0;

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值