算法练习——函数、递归和递推

在此记录一些有关函数、递归和递推的问题。所有题目均来自洛谷的题单能力提升综合题单Part1 入门阶段 - 题单 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
(实际上都没有用递推做)

[NOIP2001 普及组] 数的计算

题目描述

给出正整数 n n n,要求按如下方式构造数列:

  1. 只有一个数字 n n n 的数列是一个合法的数列。
  2. 在一个合法的数列的末尾加入一个正整数,但是这个正整数不能超过该数列最后一项的一半,可以得到一个新的合法数列。

请你求出,一共有多少个合法的数列。两个合法数列 a , b a, b a,b 不同当且仅当两数列长度不同或存在一个正整数 i ≤ ∣ a ∣ i \leq |a| ia,使得 a i ≠ b i a_i \neq b_i ai=bi

输入格式

输入只有一行一个整数,表示 n n n

输出格式

输出一行一个整数,表示合法的数列个数。

样例 #1

样例输入 #1

6

样例输出 #1

6

提示

样例 1 解释

满足条件的数列为:

  • 6 6 6
  • 6 , 1 6, 1 6,1
  • 6 , 2 6, 2 6,2
  • 6 , 3 6, 3 6,3
  • 6 , 2 , 1 6, 2, 1 6,2,1
  • 6 , 3 , 1 6, 3, 1 6,3,1

数据规模与约定

对于全部的测试点,保证 1 ≤ n ≤ 1 0 3 1 \leq n \leq 10^3 1n103

思路

注意到,对于任意一个数n,它构造数列的方式来源于它前面的数。例如,对于6来说,可以将它的数列分成下面几个部分:

  • 它本身
  • 1
  • 2
  • 3

因此,我们可以得到
f [ n ] = 1 + ∑ i = 1 n / 2 f [ i ] f[n] =1 + \sum_{i = 1}^{n / 2}f[i] f[n]=1+i=1n/2f[i]
得到这个表达式后,使用动态规划或者递归解题均可。我在这里使用递归来解决。

代码

#include<iostream>
#include<cstring>
using namespace std;

const int N = 1e3 + 10;
int n;
int f[N];

int get_nums(int u) {
    if(f[u] != -1) return f[u];
    int res = 1;
    for(int i = 1; i <= u /  2; i ++ ) {
        res += get_nums(i);
    }
    f[u] = res;
    return f[u];
}

int main() {
    cin >> n;
    
    memset(f, -1, sizeof f);
    f[1] = 1;
    f[2] = 2;
    
    cout << get_nums(n) << endl;
    return 0;
}

[NOIP2002 普及组] 选数

题目描述

已知 n n n 个整数 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn,以及 1 1 1 个整数 k k k k < n k<n k<n)。从 n n n 个整数中任选 k k k 个整数相加,可分别得到一系列的和。例如当 n = 4 n=4 n=4 k = 3 k=3 k=3 4 4 4 个整数分别为 3 , 7 , 12 , 19 3,7,12,19 3,7,12,19 时,可得全部的组合与它们的和为:

3 + 7 + 12 = 22 3+7+12=22 3+7+12=22

3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

7 + 12 + 19 = 38 7+12+19=38 7+12+19=38

3 + 12 + 19 = 34 3+12+19=34 3+12+19=34

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数: 3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

输入格式

第一行两个空格隔开的整数 n , k n,k n,k 1 ≤ n ≤ 20 1 \le n \le 20 1n20 k < n k<n k<n)。

第二行 n n n 个整数,分别为 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn 1 ≤ x i ≤ 5 × 1 0 6 1 \le x_i \le 5\times 10^6 1xi5×106)。

输出格式

输出一个整数,表示种类数。

样例 #1

样例输入 #1

4 3
3 7 12 19

样例输出 #1

1

思路

使用递归,函数dfs(int t, int sum, int d)的函数名中记录已选的个数、已选数之和、已选的最后一个数在数组中的位置。我们要保证从小到大枚举,这样就可以避免算到重复的数。

代码

#include<iostream>
using namespace std;

const int M = 22;

int n, k;
int a[M];
int ans;

bool is_prime(int u) {
    for(int i = 2; i < u / i; i ++ ) {
    	if(u % i == 0) return false;
	}
	return true;
}

void dfs(int t, int sum, int d) {
    // t表示已选t个数
    // sum表示已选数之和
    // d 表示已选的最后一个数在数组中的位置
    
    //已选k个,判断和是否是素数
    if(t == k) {
        if(is_prime(sum)) ans ++;
        return ;
    }
    
    //没选到k个,从 d + 1开始枚举
    for(int i = d + 1; i <= n; i ++ ) {
        dfs(t + 1, sum + a[i], i);
    }
}

int main() {
    cin >> n >> k;
    for(int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
    
    dfs(0, 0, 0);
    
    cout << ans << endl;
    return 0;
}

Function

题目描述

对于一个递归函数 w ( a , b , c ) w(a,b,c) w(a,b,c)

  • 如果 a ≤ 0 a \le 0 a0 b ≤ 0 b \le 0 b0 c ≤ 0 c \le 0 c0 就返回值$ 1$。
  • 如果 a > 20 a>20 a>20 b > 20 b>20 b>20 c > 20 c>20 c>20 就返回 w ( 20 , 20 , 20 ) w(20,20,20) w(20,20,20)
  • 如果 a < b a<b a<b 并且 b < c b<c b<c 就返回$ w(a,b,c-1)+w(a,b-1,c-1)-w(a,b-1,c)$。
  • 其它的情况就返回 w ( a − 1 , b , c ) + w ( a − 1 , b − 1 , c ) + w ( a − 1 , b , c − 1 ) − w ( a − 1 , b − 1 , c − 1 ) w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1) w(a1,b,c)+w(a1,b1,c)+w(a1,b,c1)w(a1,b1,c1)

这是个简单的递归函数,但实现起来可能会有些问题。当 a , b , c a,b,c a,b,c 均为 15 15 15 时,调用的次数将非常的多。你要想个办法才行。

注意:例如 w ( 30 , − 1 , 0 ) w(30,-1,0) w(30,1,0) 又满足条件 1 1 1 又满足条件 2 2 2,请按照最上面的条件来算,答案为 1 1 1

输入格式

会有若干行。

并以 − 1 , − 1 , − 1 -1,-1,-1 1,1,1 结束。

输出格式

输出若干行,每一行格式:

w(a, b, c) = ans

注意空格。

样例 #1

样例输入 #1

1 1 1
2 2 2
-1 -1 -1

样例输出 #1

w(1, 1, 1) = 2
w(2, 2, 2) = 4

提示

数据规模与约定

保证输入的数在 [ − 9223372036854775808 , 9223372036854775807 ] [-9223372036854775808,9223372036854775807] [9223372036854775808,9223372036854775807] 之间,并且是整数。

保证不包括 − 1 , − 1 , − 1 -1, -1, -1 1,1,1 的输入行数 T T T 满足 1 ≤ T ≤ 1 0 5 1 \leq T \leq 10 ^ 5 1T105

思路

我们注意到w实际上只需要计算0-20之间的数,因此我们可以用记忆化搜索把算到的w的值存储起来。

代码

#include<iostream>
#include<cstring>
using namespace std;

typedef long long LL;
LL w[25][25][25];

LL get_w(LL a, LL b, LL c) {
    if(a <= 0 || b <= 0 || c <= 0) return 1;
    if(a > 20 || b > 20 || c > 20 ) return get_w(20, 20, 20);
    if(w[a][b][c] != -1) return w[a][b][c];
    if (a < b && b < c) {
        w[a][b][c] =  get_w(a, b, c - 1) + get_w(a, b - 1, c - 1) - get_w(a, b - 1, c);
    }
    else w[a][b][c] = get_w(a - 1, b, c) + get_w(a - 1, b - 1, c) + get_w(a - 1, b, c - 1) - get_w(a - 1, b - 1, c - 1);
	return w[a][b][c];
}

int main() {
    memset(w, -1, sizeof w);
    while(1) {
        LL a, b, c;
        scanf("%lld%lld%lld", &a, &b, &c);
        if(a == b && b == c && a == -1) break;
        printf("w(%lld, %lld, %lld) = %lld\n", a, b, c, get_w(a, b, c));
    }
    return 0;
}

【XR-3】等差数列

题目描述

小 X 给了你一个等差数列的前两项以及项数,请你求出这个等差数列各项之和。

等差数列:对于一个 n n n 项数列 a a a,如果满足对于任意 i ∈ [ 1 , n ) i \in [1,n) i[1,n),有 a i + 1 − a i = d a_{i+1} - a_i = d ai+1ai=d,其中 d d d 为定值,则称这个数列为一个等差数列。

输入格式

一行 3 3 3 个整数 a 1 , a 2 , n a_1, a_2, n a1,a2,n,表示等差数列的第 1 , 2 1,2 1,2 项以及项数。

数据范围:

  • ∣ a 1 ∣ , ∣ a 2 ∣ ≤ 1 0 6 |a_1|,|a_2| \le 10^6 a1,a2106
  • 3 ≤ n ≤ 1 0 6 3 \le n \le 10^6 3n106

输出格式

一行一个整数,表示答案。

样例 #1

样例输入 #1

1 2 3

样例输出 #1

6

样例 #2

样例输入 #2

-5 -10 5

样例输出 #2

-75

提示

【样例 1 1 1 说明】

这个等差数列为 1 2 3,其各项之和为 6 6 6

思路

这道题我没用递归,直接用等差数列求和公式就可以了。要注意数据大小。

代码

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

int main() {
    LL a1, a2, n;
    cin >> a1 >> a2 >> n;
    LL d = a2 - a1;
    LL ans = ((a1 * 2 + (n - 1) * d) * n) / 2;
    cout << ans << endl;
    return 0;
}

台阶问题

题目描述

N N N 级台阶,你一开始在底部,每次可以向上迈 1 ∼ K 1\sim K 1K 级台阶,问到达第 N N N 级台阶有多少种不同方式。

输入格式

两个正整数 N , K N,K N,K

输出格式

一个正整数 a n s ( m o d 100003 ) ans\pmod{100003} ans(mod100003),为到达第 N N N 级台阶的不同方式数。

样例 #1

样例输入 #1

5 2

样例输出 #1

8

提示

  • 对于 20 % 20\% 20% 的数据, 1 ≤ N ≤ 10 1\leq N\leq10 1N10 1 ≤ K ≤ 3 1\leq K\leq3 1K3
  • 对于 40 % 40\% 40% 的数据, 1 ≤ N ≤ 1000 1\leq N\leq1000 1N1000
  • 对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 100000 1\leq N\leq100000 1N100000 1 ≤ K ≤ 100 1\leq K\leq100 1K100

思路

非常经典的问题。

注意到,对于第n级台阶,它来自于它前面的1~ k级台阶,因此第n级台阶的方案数应该等于前面n - k到n - 1级台阶的方案数之和。当然要注意边界情况。

使用函数dfs(int u)表示u阶台阶时的方案数,同时用一个数组把计算到的值全部存起来。

代码

#include<iostream>
using namespace std;

const int mod = 100003, N = 1e5 + 10;

int n, k;

int df[N];

//表示u阶台阶时的方案数 
int dfs(int u) {
    if(df[u] != 0) return df[u];
    int res = 0;
    for(int i = 1; i <= k && u - i >= 0; i ++ ) {
    	res = (res + dfs(u - i)) % mod;
	}
	df[u] = res;
	return res;
}

int main() {
    cin >> n >> k;
    if(k > n) k = n; 
    df[0] = 1;
    cout << dfs(n) << endl;
    return 0;
}

[NOIP2001 提高组] 数的划分

题目描述

将整数 n n n 分成 k k k 份,且每份不能为空,任意两个方案不相同(不考虑顺序)。

例如: n = 7 n=7 n=7 k = 3 k=3 k=3,下面三种分法被认为是相同的。

1 , 1 , 5 1,1,5 1,1,5;
1 , 5 , 1 1,5,1 1,5,1;
5 , 1 , 1 5,1,1 5,1,1.

问有多少种不同的分法。

输入格式

n , k n,k n,k 6 < n ≤ 200 6<n \le 200 6<n200 2 ≤ k ≤ 6 2 \le k \le 6 2k6

输出格式

1 1 1 个整数,即不同的分法。

样例 #1

样例输入 #1

7 3

样例输出 #1

4

提示

四种分法为:
1 , 1 , 5 1,1,5 1,1,5;
1 , 2 , 4 1,2,4 1,2,4;
1 , 3 , 3 1,3,3 1,3,3;
2 , 2 , 3 2,2,3 2,2,3.

【题目来源】

NOIP 2001 提高组第二题

思路

用递归来做。用一个函数dfs(int x, int sum, int d)来递归计算,其中上一个数为x,目前总和为sum,还需要递归的次数为d。当d = 0时,满足sum == n时要加在总数中。

代码

#include<iostream>
using namespace std;

int n, k;
int ans;

//表示上一个数为x,目前总和为sum,还需要递归的次数为d
void dfs(int x, int sum, int d) {
    if(d == 0) {
        if(sum == n) ans ++;
        return;
    }
    for(int i = x; i <= n && i + sum <= n; i ++ ) {
        dfs(i, sum + i, d - 1);
    }
    return;
}

int main() {
    cin >> n >> k;
    
    dfs(1, 0, k); 
    cout << ans << endl;
    return 0;
}

终于结束的起点

题目背景

终于结束的起点
终于写下句点
终于我们告别
终于我们又回到原点
……

一个个 OIer 的竞赛生涯总是从一场 NOIp 开始,大多也在一场 NOIp 中结束,好似一次次轮回在不断上演。
如果这次 NOIp 是你的起点,那么祝你的 OI 生涯如同夏花般绚烂。
如果这次 NOIp 是你的终点,那么祝你的 OI 回忆宛若繁星般璀璨。
也许这是你最后一次在洛谷上打比赛,也许不是。
不过,无论如何,祝你在一周后的比赛里,好运。

当然,这道题也和轮回有关系。

题目描述

广为人知的斐波拉契数列 f i b ( n ) \mathrm{fib}(n) fib(n) 是这么计算的

f i b ( n ) = { 0 , n = 0 1 , n = 1 f i b ( n − 1 ) + f i b ( n − 2 ) , n > 1 \mathrm{fib}(n)=\begin{cases} 0,& n=0 \\ 1,& n=1 \\ \mathrm{fib}(n-1) + \mathrm{fib}(n-2),& n>1 \end{cases} fib(n)= 0,1,fib(n1)+fib(n2),n=0n=1n>1

也就是 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 ⋯ 0, 1, 1, 2, 3, 5, 8, 13 \cdots 0,1,1,2,3,5,8,13,每一项都是前两项之和。

小 F 发现,如果把斐波拉契数列的每一项对任意大于 1 1 1 的正整数 M M M 取模的时候,数列都会产生循环。

当然,小 F 很快就明白了,因为 ( f i b ( n − 1 )   m o d   M \mathrm{fib}(n - 1) \bmod M fib(n1)modM) 和 ( f i b ( n − 2 )   m o d   M ) \mathrm{fib}(n - 2) \bmod M) fib(n2)modM) 最多只有 M 2 M ^ 2 M2 种取值,所以在 M 2 M ^ 2 M2 次计算后一定出现过循环。

甚至更一般地,我们可以证明,无论取什么模数 M M M,最终模 M M M 下的斐波拉契数列都会是 0 , 1 , ⋯   , 0 , 1 , ⋯ 0, 1, \cdots, 0, 1, \cdots 0,1,,0,1,

现在,给你一个模数 M M M,请你求出最小的 n > 0 n > 0 n>0,使得 f i b ( n )   m o d   M = 0 , f i b ( n + 1 )   m o d   M = 1 \mathrm{fib}(n) \bmod M = 0, \mathrm{fib}(n + 1) \bmod M = 1 fib(n)modM=0,fib(n+1)modM=1

输入格式

输入一行一个正整数 M M M

输出格式

输出一行一个正整数 n n n

样例 #1

样例输入 #1

2

样例输出 #1

3

样例 #2

样例输入 #2

6

样例输出 #2

24

提示

样例 1 解释

斐波拉契数列为 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , ⋯ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, \cdots 0,1,1,2,3,5,8,13,21,34,,在对 2 2 2 取模后结果为 0 , 1 , 1 , 0 , 1 , 1 , 0 , 1 , 1 , 0 , ⋯ 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, \cdots 0,1,1,0,1,1,0,1,1,0,

我们可以发现,当 n = 3 n = 3 n=3 时, f ( n )   m o d   2 = 0 , f ( n + 1 )   m o d   2 = 1 f(n) \bmod 2= 0, f(n + 1) \bmod 2 = 1 f(n)mod2=0,f(n+1)mod2=1,也就是我们要求的 n n n 的最小值。

数据范围

对于 30 % 30\% 30% 的数据, M ≤ 18 M \leq 18 M18

对于 70 % 70\% 70% 的数据, M ≤ 2018 M \leq 2018 M2018

对于 100 % 100\% 100% 的数据, 2 ≤ M ≤ 706150 = 0xAC666 2 \leq M \leq 706150=\verb!0xAC666! 2M706150=0xAC666

提示

如果你还不知道什么是取模 (   m o d   ) (\bmod) (mod),那我也很乐意告诉你,模运算是求整数除法得到的余数,也就是竖式除法最终「除不尽」的部分,也即
a   m o d   M = k    ⟺    a = b M + k   ( M > 0 , 0 ≤ k < M ) a \bmod M =k \iff a = bM + k\ (M > 0, 0 \leq k < M) amodM=ka=bM+k (M>0,0k<M)
其中 a , b , k a, b, k a,b,k 都是非负整数。

如果你使用 C / C++,你可以使用 % 来进行模运算。

如果你使用 Pascal,你可以使用 mod 来进行模运算。

思路

使用记忆化搜索,存储每个获得的fib值。要注意数的范围,都需要使用long long来存储数。

代码

#include<iostream>
using namespace std;

typedef long long LL;
const int N = 1e7 + 10;

LL f[N];
LL m;

LL fib(LL u) {
    if(f[u]) return f[u];
    if(u == 1 || u == 2) return f[u] = 1 % m;
    f[u] = (fib(u - 1) + fib(u - 2)) % m;
    return f[u];
}

int main () {
    scanf("%lld", &m);
    for(LL i = 2; ; i ++ ) {
        if(fib(i) % m == 0 && fib(i + 1) % m == 1) {
            printf("%lld", i);
            return 0;
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值