【CSP考题扩展】递推求解(1)

P1255 数楼梯

题目链接

解题思路

这个题目是求解斐波那契数列的第n项,但因为n的值可能很大(最大可达5000),直接使用普通的整数类型来存储计算结果是不现实的,因此需要使用高精度计算。解题思路如下:

  1. 初始化: 首先,初始化斐波那契数列的第1项和第2项。因为斐波那契数列的定义是第n项等于其前两项之和(对于n>2),所以可以设f[1][1]=1和f[2][1]=2,表示第1项和第2项的值分别是1和2。这里使用二维数组f来存储每一项的每一位数字,f[k][i]表示第k项的第i位数字。

  2. 高精度加法: 实现一个高精度加法函数highPrecisionAdd,用于计算斐波那契数列的每一项。这个函数的工作原理是将前两项的对应位相加,并处理进位。具体来说,从低位到高位逐位相加,如果某一位的结果大于或等于10,则将这一位的结果减去10,并将1加到下一位上(即处理进位)。这个过程可能会导致数字的总长度增加,所以需要动态调整存储的长度。

  3. 计算第n项: 使用一个循环,从第3项开始计算,直到第n项。每一项都通过调用highPrecisionAdd函数计算得到,这个函数利用了斐波那契数列的性质:每一项等于其前两项之和。

  4. 输出结果: 最后,由于使用的是数组逆序存储数字(即数组的第一位存储的是最低位),因此输出第n项的值时需要从最高位开始逆序输出。

通过上述步骤,即可求出斐波那契数列的第n项,并以字符串的形式输出,解决了大数问题。

完整代码

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

const int MAX = 5003;
int f[MAX][MAX]; // f[k][i] 表示第k项斐波那契数的第i位数字
int n, len = 1;

// 高精度加法函数,用于计算斐波那契数列的第k项
void highPrecisionAdd(int k) {
    // 第一步:按位相加。将第 k-1 项和第 k-2 项对应的每一位进行相加
    for (int i = 1; i <= len; ++i) {
        f[k][i] = f[k - 1][i] + f[k - 2][i];
    }

    // 第二步:处理进位。遍历每一位,处理相加后的进位
    for (int i = 1; i <= len; ++i) {
        if (f[k][i] >= 10) { // 如果当前位的值大于等于10,需要进位
            f[k][i + 1] += f[k][i] / 10; // 将进位加到下一位
            f[k][i] %= 10; // 更新当前位的值为进位后的余数

            // 如果最高位也发生了进位,长度增加1
            // 注意:这里的条件判断是为了防止重复增加长度,只有在最高位产生新的进位时才增加
            if (f[k][len + 1]) {
                len++;
            }
        }
    }
}


// 初始化斐波那契数列的前两项并计算第n项
void calculateFibonacci(int n) {
    f[1][1] = 1; // 第1项初始化
    f[2][1] = 2; // 第2项初始化
    for (int i = 3; i <= n; ++i) {
        highPrecisionAdd(i);
    }
}

int main() {
    cin >> n;
    calculateFibonacci(n);
    for (int i = len; i >= 1; --i) { // 逆序输出第n项
        cout << f[n][i];
    }
    return 0;
}

P1002 [NOIP2002 普及组] 过河卒 题解

题目链接

解题思路

可以通过动态规划方法进行解决。本问题特别考虑了棋盘上存在障碍(如“马”所在的位置)对路径选择的限制。为便于分析与计算,首先将棋盘坐标系统原点从(0,0)平移至(1,1),使得所有涉及的坐标均为正整数。

定义一个动态规划状态 f ( i , j ) f(i,j) f(i,j) 表示从起始点(1,1)到达坐标为 ( i , j ) (i,j) (i,j) 的格子的路径数量。考虑到在无障碍的情况下,任一格子只能从其左侧或上方的相邻格子到达,因此,状态转移方程可表达为 f ( i , j ) = f ( i − 1 , j ) + f ( i , j − 1 ) f(i,j) = f(i-1,j) + f(i,j-1) f(i,j)=f(i1,j)+f(i,j1)。其中, i i i j j j 分别表示格子的行和列坐标,满足 i , j > 1 i, j > 1 i,j>1。对于基础情况,为使方程有效,需初始化 f ( 1 , 1 ) = 1 f(1,1) = 1 f(1,1)=1,表明从(1,1)至(1,1)有一条路径。此初始化相当于人为设置 f ( 1 , 0 ) = 1 f(1,0) = 1 f(1,0)=1 f ( 0 , 1 ) = 1 f(0,1) = 1 f(0,1)=1,以符合状态转移方程的逻辑。

进一步考虑“马”的影响,假设“马”阻挡了某个格子 ( x , y ) (x,y) (x,y),使其不可达,此时应将 f ( x , y ) = 0 f(x,y) = 0 f(x,y)=0,即直接跳过该点。在实际计算过程中,为避免数组越界(特别是在检查“马”阻挡的格子时),所有坐标值应相应增加,确保所有参考的坐标位置有效。

最终求解目标是计算到达指定终点 ( n , m ) (n,m) (n,m) 的路径总数,即 f ( n , m ) f(n,m) f(n,m)。考虑到路径数量可能非常大,应使用长整型(如 long long)以避免溢出。

完整代码

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

vector<int>horseX = { 0, -2, -1, 1, 2, 2, 1, -1, -2 };
vector<int>horseY = { 0, 1, 2, 2, 1, -1, -2, -2, -1 };
vector<vector<long long>>f(40, vector<long long>(40));
vector<vector<long long>>stop(40, vector<long long>(40));
int x, y, hx, hy;

int main() {
    cin >> x >> y >> hx >> hy;
    x += 2, y += 2, hx += 2, hy += 2;
    f[2][1] = 1; // 为了初始化f[2][2]

    // 马拦卒
    stop[hx][hy] = 1;
    for (int i = 1; i <= 8; i++)
    {
        stop[hx + horseX[i]][hy + horseY[i]] = 1;
    }

    // 递归
    for (int i = 2; i <= x; i++) {
        for (int j = 2; j <= y; j++) {
            if (stop[i][j]) continue; // 被马拦住就直接跳过(0)
            f[i][j] = f[i - 1][j] + f[i][j - 1];
        }
    }

    cout << f[x][y];
    return 0;
}

R153198445 记录详情

题目链接

解题思路

代码的核心是通过递归实现对所有可能的栈操作(入栈和出栈)序列的遍历,以计算出从初始序列1, 2, ..., n通过栈操作得到的所有可能的输出序列的总数。

1.状态(i, j)定义

  • i:有i个元素未入栈中
  • j:j个元素在栈中

2.递归的基案和递推

  • 基案:当所有元素都已经进入栈中,即i = 0时,这时无论栈中j如何变化,已经没有其他操作可以执行,因为没有更多的元素可以入栈了,此时定义只有一种输出序列,即递归的基础情况返回1。

  • 递推:从状态(i, j)出发,我们有两个选择:

    1. 入栈操作:若还有未进栈的元素(即i > 0),可以选择将一个元素入栈。这相当于做了一个决策,将一个元素从未处理序列移至栈中,因此状态转移为(i-1, j+1)
    2. 出栈操作:若栈内有元素(即j > 0),可以选择将栈顶元素出栈。这会将一个元素从栈中移除,加入到最终的输出序列中,因此状态转移为(i, j-1)

3.记忆化搜索

为了避免在递归过程中重复计算相同状态的结果,代码中使用了一个二维数组f[MAX_N][MAX_N]作为缓存,来存储每个状态(i, j)的结果。在进入每个状态时,首先检查这个状态是否已经被计算过(即f[i][j]是否非零):

  • 如果已计算(f[i][j]非零),则直接返回该状态的结果,避免重复计算。
  • 如果未计算,按照递归逻辑进行计算,并将计算结果存入f[i][j]中,以供后续使用。

4.递归逻辑

在每次递归调用中,根据当前状态(i, j),尝试进行入栈和出栈操作,并递归地计算这两个操作导致的新状态的解,将这些解加起来得到当前状态的解。

最终,通过从初始状态(n, 0)开始的递归调用,我们可以计算出所有可能的操作序列,并累加得到由操作数序列1, 2, ..., n可能得到的所有输出序列的总数。

5.代码执行流程

  1. 初始化全局变量n和二维数组f
  2. 读入元素总数n
  3. 调用dfs(n, 0)开始从初始状态进行递归计算。
  4. 输出从初始序列通过栈操作可能得到的所有输出序列的总数。

完整代码

#include <iostream>
#define MAX_N 20
#define ll long long
using namespace std;

int n; 
ll f[MAX_N][MAX_N]; // i 个未进栈元素 j 个栈内元素时

ll dfs(int i, int j) {
    if (f[i][j]) return f[i][j]; 
    if (i == 0) return 1; // 所有元素都已进栈(i = 0),只有一种排列方式
    
    if (j > 0) f[i][j] += dfs(i, j - 1); // 栈中有元素(j > 0): 出栈操作,将结果累加到当前状态的解中
    if (i > 0) f[i][j] += dfs(i - 1, j + 1); // 还有元素未进栈(i > 0),入栈操作

    return f[i][j];
}

int main() {
    cin >> n;
    cout<<dfs(n, 0); 
    return 0;
}
  • 12
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值