java 洛谷题单【算法1-4】递推与递归

P1255 数楼梯

解题思路

经典递归问题,参考斐波那契数列。

一次上一阶或者两阶,那么当一层楼时有一种上法,两层楼有两种上法,由此得到初始值。

注意题目标签带有高精度,说明需要设置高精度数据。

类似题目可以参考我的博客洛谷题单【1-1】模拟与高精度

提交代码时需要把语言切换到java21,java8无法编译。

import java.math.BigInteger;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int n = input.nextInt();
        System.out.println(func(n));
    }

    public static BigInteger func(int n) {
        if (n == 1) return BigInteger.ONE;
        if (n == 2) return BigInteger.TWO;
        BigInteger[] num = new BigInteger[n];
        num[0] = BigInteger.ONE;
        num[1] = BigInteger.TWO;

        for (int i = 2; i < n; i++) {
            num[i] = num[i - 1].add(num[i - 2]);
        }
        return num[n - 1];
    }
}

 P1002 [NOIP2002 普及组] 过河卒

解题思路

这是一个比较典型的dp问题。状态转移方程:dp[i][j]=dp[i-1][j] + dp[i][j-1].

每一次转移只需要提供 dp[i-1][j]dp[i][j-1].

如图,只需要提供卒的上一个状态即可。即保留 i i-1 行的数据即可。

这是dp问题优化中的滚动数组优化,滚动数组优化为递推问题中的常用空间优化手段,如果每次递推都由上一次状态转移那么我们可以将空间降低一个维度。

具体概念与解释可以参考这篇博客:01背包详解,状态设计,滚组优化,通用问题求解

f[i & 1][j] = f[(i - 1) & 1][j] + f[i & 1][j - 1];

这里, f[i & 1][j] 表示第 i 行第 j 列的元素值, f[(i - 1) & 1][j] 表示上一行的第 j 列元素值,

f[i & 1][j - 1]表示上一行的第 j - 1 列元素值。

  • 输入处理和偏移

    • 首先从输入中读取目标点bx, by和马的位置mx, my
    • 为了简化边界处理,将所有坐标增加2,防止负数索引的出现。
  • 初始化

    • 创建一个二维数组f来记录到达每个点的路径数。
    • 创建一个二维数组s来记录不可达的点(被马控制的点)。
  • 设置马的控制点

    • 计算马的控制点,并在s数组中标记这些点。
  • 动态规划计算路径

    • 使用动态规划填充f数组,根据状态转移方程计算从起点到每个点的路径数。
    • 如果某个点被马控制,则路径数为0。
    • 路径数为左边点和上边点路径数之和。
  • 输出结果

    • 最后输出到达终点的路径数。
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int bx = input.nextInt();  // 终点 x 坐标
        int by = input.nextInt();  // 终点 y 坐标
        int mx = input.nextInt();  // 马的 x 坐标
        int my = input.nextInt();  // 马的 y 坐标

        // 坐标偏移,防止负数索引问题
        bx += 2;
        by += 2;
        mx += 2;
        my += 2;

        // 初始化路径计数数组和马控制点数组
        long[][] f = new long[2][40];
        boolean[][] s = new boolean[40][40];

        // 马的控制点相对偏移量
        int[] fx = {0, -2, -1, 1, 2, 2, 1, -1, -2};
        int[] fy = {0, 1, 2, 2, 1, -1, -2, -2, -1};

        // 设置起点路径数为1
        f[1][2] = 1;

        // 标记马的控制点
        s[mx][my] = true;
        for (int i = 1; i <= 8; i++) {
            int x = mx + fx[i];
            int y = my + fy[i];
            if (x >= 0 && x < 40 && y >= 0 && y < 40) {
                s[x][y] = true;
            }
        }

        // 动态规划计算路径
        for (int i = 2; i <= bx; i++) {
            for (int j = 2; j <= by; j++) {
                if (s[i][j]) {
                    f[i & 1][j] = 0; // 如果是马控制点,路径数为0
                    continue;
                }
                // 路径数等于左边点和上边点路径数之和
                f[i & 1][j] = f[(i - 1) & 1][j] + f[i & 1][j - 1];
            }
        }

        // 输出结果
        System.out.println(f[bx & 1][by]);
    }
}

P1044 [NOIP2003 普及组] 栈

解题思路

定义二维数组, i 表示队列中的数, j 表示出栈数, arr[i][j] 表示所有情况数。

  还有数论做法,参考数论做法题解

DP做法 

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int n = input.nextInt();
        int[][] arr = new int[20][20];

        for (int i = 0; i <= n; i++) {
            arr[0][i] = 1;
        }

        for (int i = 1; i <= n; i++) {
            for (int j = i; j <= n; j++) {
                if (i == j) {
                    arr[i][j] = arr[i - 1][j];
                }else {
                    arr[i][j] = arr[i - 1][j] + arr[i][j - 1];
                }
            }
        }
        System.out.println(arr[n][n]);
    }

}

 DFS记忆化搜索做法

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int n = input.nextInt();
        int[][] arr = new int[20][20];
        System.out.println(dfs(n, 0, arr));

    }

    public static int dfs(int i, int j, int[][] arr) {
        if (arr[i][j] != 0)  {
            return arr[i][j];
        }
        if (i == 0) return 1;
        if (j > 0) {
            arr[i][j] += dfs(i, j - 1, arr);
        }
        arr[i][j] += dfs(i - 1, j + 1, arr);
        return arr[i][j];
    }
}

 P1028 [NOIP2001 普及组] 数的计算

解题思路

我们需要计算从一个数字 n 开始的所有合法数列的数量。根据题目要求,合法数列的构造规则是:

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

为了高效地计算,我们使用递归和动态规划(记忆化搜索)的方法。动态规划通过缓存已经计算过的子问题结果来避免重复计算,从而提高效率。

具体步骤如下:

  1. 递归函数定义:定义一个递归函数 countSequences(int current) 来计算从 current 开始的所有合法数列的数量。
  2. 记忆化数组:使用一个数组 dp 来缓存已经计算过的结果。dp[i] 表示从数字 i 开始的合法数列的数量。
  3. 递归终止条件:如果 current 已经小于等于 0,返回 0,因为没有更多的数可以加入。
  4. 递归计算:从当前数字开始,递归计算可以加入的所有合法数列的数量,并将结果累加。
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();

        int[] dp = new int[n + 1]; // 初始化记忆化数组,大小为 n + 1
        System.out.println(countSequences(n, dp)); // 输出从 n 开始的所有合法数列的数量
    }

    /**
     * 递归函数,用于计算从 current 开始的所有合法数列的数量
     * @param current 当前数列的最后一项
     * @param dp 记忆化数组,用于缓存已经计算过的结果
     * @return 从 current 开始的所有合法数列的数量
     */
    public static int countSequences(int current, int[] dp) {
        if (current == 0) return 0; // 如果 current 为 0,返回 0
        if (dp[current] != 0) return dp[current]; // 如果已经计算过,直接返回缓存结果

        int count = 1; // 初始化计数,数列只有一个数字 current 也是一个合法数列

        // 计算可以加入的数字并递归计算子问题
        for (int next = current / 2; next > 0; next--) {
            count += countSequences(next, dp); // 递归计算并累加结果
        }

        dp[current] = count; // 将计算结果缓存到 dp 数组中
        return count; // 返回计算的合法数列数量
    }
}

P1464 Function

解题思路

  • 问题描述

    • 实现一个函数计算 w(a, b, c) 的值,按照特定的规则进行计算。
    • w(a, b, c) 的定义:
      • 如果任意一个参数 ≤0\leq 0≤0,返回 1。
      • 如果任意一个参数 >20> 20>20,将该参数当作 20 来处理。
      • 如果 a<b<ca < b < ca<b<c,则 w(a,b,c)=w[a][b][c−1]+w[a][b−1][c−1]−w[a][b−1][c]w(a, b, c) = w[a][b][c-1] + w[a][b-1][c-1] - w[a][b-1][c]w(a,b,c)=w[a][b][c−1]+w[a][b−1][c−1]−w[a][b−1][c]。
      • 否则,w(a,b,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, b, 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,b,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 存储已经计算过的值,避免重复计算。
    • 在主函数中预先计算并存储所有可能的 w(i,j,k)w(i, j, k)w(i,j,k) 的值。
  • 输入处理

    • 使用 Scanner 类读取输入,并在输入值为 -1, -1, -1 时结束。
import java.util.Scanner;

public class Main {
    // 定义常量 N 为 32 (2^5) 用于数组大小
    static final int N = 1 << 5;
    // 三维数组 w 用于存储计算结果
    static long[][][] w = new long[N][N][N];

    // 计算 w(a, b, c) 的递归函数
    public static long W(long a, long b, long c) {
        // 如果任意一个参数 <= 0,返回 1
        if (a <= 0 || b <= 0 || c <= 0) return 1;

        // 如果任意一个参数 > 20,将其视为 20
        if (a > 20 || b > 20 || c > 20) return w[20][20][20];

        // 如果 a < b < c,使用特定公式计算
        if (a < b && b < c) return w[(int)a][(int)b][(int)c - 1]
                + w[(int)a][(int)b - 1][(int)c - 1] - w[(int)a][(int)b - 1][(int)c];

        return w[(int)a - 1][(int)b][(int)c] + w[(int)a - 1][(int)b - 1][(int)c]
                + w[(int)a - 1][(int)b][(int)c - 1] - w[(int)a - 1][(int)b - 1][(int)c - 1];
    }

    public static void main(String[] args) {
        // 预计算 w(i, j, k) 的值并存储在数组 w 中
        for (int i = 0; i <= 20; ++i) {
            for (int j = 0; j <= 20; ++j) {
                for (int k = 0; k <= 20; ++k) {
                    w[i][j][k] = W(i, j, k);
                }
            }
        }

        Scanner input = new Scanner(System.in);
        long a, b, c;

        // 循环读取输入并计算 w(a, b, c) 的值
        while (true) {
            a = input.nextLong();
            b = input.nextLong();
            c = input.nextLong();
            // 当输入为 -1, -1, -1 时结束
            if (a == -1 && b == -1 && c == -1) break;
            // 输出结果
            System.out.println("w(" + a + ", " + b + ", " + c + ") = " + W(a, b, c));
        }
        
    }
}

P1928 外星密码 

解题思路

java的hasNest()方法dddd。该方法可以参考这个博客Java的hasNext()方法

  • 初始化和输入读取

    • Scanner input = new Scanner(System.in); 读取用户输入的字符串。
    • String s = input.next(); 将输入的字符串赋值给变量 s
    • int[] start = new int[105]; 初始化一个数组,用于记录所有左括号 '[' 的位置。
    • int sum = 0; 初始化计数器,用于记录左括号的数量。
  • 遍历字符串记录左括号的位置

    • 使用一个循环 for (int i = 0; i < s.length(); i++) 遍历字符串 s
    • 如果字符是左括号 '[',则记录其位置到 start 数组中,同时 sum 递增。
  • 从最后一个左括号开始,向前处理

    • sum 开始,依次处理每个左括号位置。
    • end 用于计算从左括号到右括号之间的字符数量。
    • num 用于记录左括号后面的数字(表示重复的次数)。
    • temp 用于存储左括号后面的字符串部分。
    • now 用于存储将要插入的新字符串(即 temp 重复 num 次后的结果)。
  • 处理括号中的内容

    • 从左括号后的字符开始,直到遇到右括号 ']',逐个字符处理:
      • 如果字符是数字,则更新 num
      • 否则,将字符添加到 temp 中。
    • 计算字符的总长度 end
  • 生成新字符串

    • 使用 String.valueOf(temp).repeat(Math.max(0, num))temp 重复 num 次,并赋值给 now
    • 使用 StringBuilder 删除原字符串中从左括号到右括号之间的部分,并插入新生成的 now
    • 更新字符串 s 为新生成的字符串。
  • 输出最终结果

    • 输出处理后的字符串 s
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        String s = input.next();
        int[] start = new int[105];
        int sum = 0;
        int end, num;
        StringBuilder temp;
        StringBuilder now;

        // 遍历字符串,记录左括号的位置和数量
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '[') {
                start[++sum] = i;
            }
        }

        // 从最后一个左括号开始,向前遍历
        for (int i = sum; i > 0; i--) {
            end = 2;
            num = 0;
            temp = new StringBuilder();
            now = new StringBuilder();
            // 遍历左括号后面的字符,直到遇到右括号
            for (int j = start[i] + 1; s.charAt(j) != ']'; j++) {
                if (s.charAt(j) >= '0' && s.charAt(j) <= '9') {
                    num = num * 10 + (s.charAt(j) - '0');
                } else {
                    temp.append(s.charAt(j));
                }
                end++;
            }

            // 将左括号后面的字符复制num次,添加到now
            now.append(String.valueOf(temp).repeat(Math.max(0, num)));

            // 删除原字符串中左括号及其后面的内容,将now插入到原字符串中
            StringBuilder sb = new StringBuilder(s);
            sb.delete(start[i], start[i] + end);
            sb.insert(start[i], now);
            s = sb.toString();
        }

        System.out.println(s);
    }
}

P2437 蜜蜂路线

解题思路

F(n)=F(n-1)+F(n-2)

高精度斐波那契数列,参考P1255数楼梯

import java.math.BigInteger;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int m = input.nextInt();
        int n = input.nextInt();

        System.out.println(func(m, n));
    }

    public static BigInteger func(int m, int n) {
        BigInteger a = BigInteger.ONE;
        BigInteger b = BigInteger.ONE;
        BigInteger c;
        for (int i = 2; i <= n - m; i++) {
            c = a.add(b);
            a = b;
            b = c;
        }
        return b;
    }
}

 P1164 小A点菜

解题思路

动态规划数组的定义:

  • dp[i][j] 表示考虑前 i 道菜,正好花费 j 元的点菜方案数。

初始化:

  • Java 中数组默认初始化为 0。我们通常需要为 dp[0][0] 设置为 1,意味着使用 0 道菜花费 0 元有一种方案(什么都不点)。

转移方程:

  1. 价格恰好等于当前花费:如果当前菜的价格恰好等于 j(花费),那么除了之前的方案数 dp[i-1][j] 外,还可以直接点这道菜,因此 dp[i][j] += 1
  2. 价格小于当前花费:如果当前菜的价格小于 j,那么除了之前的方案数 dp[i-1][j] 外,还可以通过点这道菜之后,再看看用剩下的钱(j - arr[i])可以有多少种点菜方案,即 dp[i][j] += dp[i-1][j-arr[i]]
  3. 价格大于当前花费:如果当前菜的价格大于 j,那么不能点这道菜,只能继承之前没有点这道菜时的方案数,即 dp[i][j] = dp[i-1][j]

整体逻辑:

  • 遍历每道菜,更新每个可能的花费的方案数。
  • 对于每个可能的花费,根据上述情况更新方案数。

最后输出:

  • 输出 dp[n][m],即考虑所有菜,正好花费 m 元的方案数。
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int n = input.nextInt();
        int m = input.nextInt();


        int[] arr = new int[n + 1];
        // 存储点菜方案数:dp[i][j]
        // 前 i 道菜花光 j 元的点菜方案数
        int[][] dp = new int[1000][1000];


        for (int i = 1; i <= n; i++) {
            arr[i] = input.nextInt();
        }
        
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                // 第 i 道菜的价格等于 j 元
                if (j == arr[i]) {
                    dp[i][j] = dp[i - 1][j] + 1;
                }
                // 第 i 道菜的价格小于 j 元
                // 加上减去arr[i]后的余额可点菜数
                if (j > arr[i]) {
                    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - arr[i]];
                }
                // 第 i 道菜的价格大于 j 元
                if (j < arr[i]) {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }

        System.out.println(dp[n][m]);
    }

}

P1036 [NOIP2002 普及组] 选数

【算法1-3】 暴力枚举的原题:java【算法1-3】暴力枚举全AC代码

P1990 覆盖墙壁

该思路摘自题解 P1990 【覆盖墙壁】


解题思路 (存在部分修改以及图的补充)

 题意:用如下两种砖块(可旋转)填充 2×n 的墙壁,求出不重复方案数,结果对 10^{_{4}} 取模(输出覆盖方法的后4位)。

按照惯例,定义 Fn​ 为填满 2×n 墙壁的方案总数,边界条件 F[0]​=1,对于 k<0,F[k]​=0。(F[0]​ 表示无需再填,F[k​](k<0) 表示无意义情况)

考虑最后放的情况:

  1. 放 1 个 2×1 的砖块(竖放):显然它的方案数为 F[n−1]​;(图 1)preF_{n} = \sum_{i=0}^{n}F_{i}

  2. 放 2 个 2×1 的砖块(横放):方案数为 F[n−2]​;(图 2)

  3. 放 1 个 L 型砖块(因为该砖块可以翻转着放,所以这样放的总方案数要  2):这么填会带来 1 个格子的突出,如何消去这个突出?

    • 再放 1 个 L 型砖块,恰好消去突出,方案数 F[n−3]​;(图 3-1)
    • 横放 1 个 2×1 的砖块,再放 L 型砖块,方案数 F[n−4]​(图 3-2);
    • 2×1 的砖块可以交替着放下去,再补上一个 L 型砖块,从而消去这个突出。
    • 直到 2×1 砖块和 L 型砖块恰好填满墙壁(图3-3)。

    综上,最后放 1 个 L 型砖块得到的方案数为 2×(F[n−3]​+F[n−4]​+⋯+F[0]​)(已经乘了 2)。

综合 3 种情况,得:F_{n} = \left (\sum_{i=0}^{n-1} F_{i} \right ) + \left ( \sum_{i=0}^{n-3} F_{i} \right )

如果每次都重新算前 n−1 个方案总和,很明显会超时。当然很容易能想到前缀和:

定义 preF_{n} = \sum_{i=0}^{n}F_{i} ,即前 n 个方案数之和。对于 k<0,preF_{k} = 0

很明显得出 preF_{n} = preF_{n-1} + F_{n}​。

那么之前的式子可以变成:F_{n} = preF_{n-1} + preF_{n-3}

 (图3-3)

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        int n = input.nextInt();

        int[] f = new int[10000000];
        int[] pre = new int[10000000];

        f[0] = 1;
        pre[0] = 1;

        for (int i = 1; i <= n; i++) {
            f[i] = (i - 1 < 0 ? 0 : pre[i - 1]) + (i - 3 < 0 ? 0 : pre[i - 3]);
            f[i] %= 10000;
            pre[i] = pre[i - 1] + f[i];
            pre[i] %= 10000;
        }

        System.out.println(f[n]);

    }

}

P3612 [USACO17JAN] Secret Cow Code S

解题思路

  • 输入处理
    • 读取输入的字符串 s 和目标位置 n
  • 初始长度计算
    • 计算字符串 s 的长度 l
  • 扩展字符串长度
    • 将字符串长度 l 不断乘以2,直到 l 大于等于 n
  • 查找目标字符
    • 利用反向推导的方法,从无限扩展字符串中的位置 n 反向找到原始字符串中的对应位置。
    • 每次缩小 l,直到 n 小于或等于原始字符串长度 temp
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        // 读取输入的字符串 s 和位置 n
        String s = input.next();
        long n = input.nextLong();

        // 获取字符串 s 的长度
        long l = s.length();
        long temp = l;

        // 扩展字符串长度,直到 l 大于或等于 n
        while (l < n) {
            l = l * 2;
        }
        l = l / 2; // 回退到刚好小于 n 的长度

        // 反向推导找到位置 n 对应原始字符串的位置
        while (n > temp) {
            if (n == l) {
                n = n / 2; // 如果 n 正好在 l 上,则移动到 l 的前一部分
            } else if (n - l > 1) {
                n = n - l - 1; // 如果 n 超过 l,则在右半部分,减去 l 和一个位置
            } else if (n - l == 1) {
                n = l; // 如果 n 超过 l 刚好一个位置,则移动到 l 上
            }

            l /= 2; // 缩小 l 为原来的一半,继续查找
        }

        // 输出在位置 n 处的字符
        System.out.printf("%c", s.charAt((int) (n - 1)));
    }
}

P1259 黑白棋子的移动

解题思路

数据范围是4-100。先模拟n=4的情况。

 再模拟n=5的情况。

 

 从图中可以看出该问题的关键所在,就是一个递归问题,基本思路:

  • 初始化棋盘:根据输入的 n 初始化棋盘,将前 n 个位置放置白子 'o',后 n 个位置放置黑子 '*',最后两个位置设为空 '-'.。

  • 打印棋盘:在每一步操作后打印当前棋盘状态。

  • 移动棋子:将棋盘上的指定位置的两个棋子移动到当前空位的位置,并更新空位位置。

  • 递归移动:通过递归的方式处理移动操作,当 n 等于 4 时,进行特殊处理,其他情况通过递归处理 n-1 个棋子的移动。

import java.util.*;

public class Main {
    // 全局变量,n表示棋子对数,position表示空位的起始位置
    public static int n, position;
    // 存储棋子状态的字符数组
    public static char[] c = new char[1010];

    // 打印当前棋盘状态的方法
    public static void print() {
        // 打印棋盘上1到2n+2的位置的字符
        for (int i = 1; i <= 2 * n + 2; i++) {
            System.out.print(c[i]);
        }
        System.out.println();
    }

    // 初始化棋盘状态的方法
    public static void init(int n) {
        position = 2 * n + 1; // 初始化空位起始位置
        // 1到n位置存储白子'o'
        for (int i = 1; i <= n; i++) {
            c[i] = 'o';
        }
        // n+1到2n位置存储黑子'*'
        for (int i = n + 1; i <= 2 * n; i++) {
            c[i] = '*';
        }
        // 最后两格存储'-'表示空位
        c[2 * n + 1] = '-';
        c[2 * n + 2] = '-';
        // 打印初始化后的棋盘状态
        print();
    }

    // 移动棋子的方法,k表示要移动的位置
    public static void move(int k) {
        // 将当前位置k的两个棋子移动到空位位置
        for (int j = 0; j <= 1; j++) {
            c[position + j] = c[k + j];
            c[k + j] = '-'; // 将原位置设置为空
        }
        position = k; // 更新空位起始位置
        // 打印移动后的棋盘状态
        print();
    }

    // 递归处理移动过程的方法
    public static void func(int n) {
        if (n == 4) {
            // 特殊处理n等于4的情况
            move(4);
            move(8);
            move(2);
            move(7);
            move(1);
        } else {
            // 一般情况的移动
            move(n);
            move(2 * n - 1);
            // 递归调用,处理n-1个棋子的移动
            func(n - 1);
        }
    }
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt(); // 从输入中读取n的值
        init(n); // 初始化棋盘
        func(n); // 开始递归处理移动
    }
}

P1010 [NOIP1998 普及组] 幂次方

解题思路

二进制的计算+递归

  • 分解整数为二进制:首先,通过 decompose 函数将整数分解为二进制表示,每个位置存储该位的二进制值。
  • 递归表示幂次方:通过 representPower 函数递归地将指数表示为符合题目要求的括号表达式。例如,2^3 表示为 2(2(2))
  • 构建最终表示:在 powerRepresentation 函数中,通过遍历整数的二进制表示,构建最终的字符串结果。
  • 处理输入和输出:在 main 函数中,处理标准输入输出,调用上述函数完成整个过程。
import java.util.Scanner;

public class Main {
    // 将一个整数表示成二进制数
    private static int[] decompose(int num) {
        int[] result = new int[32];  // 最多支持32位二进制表示
        int index = 0;
        while (num > 0) {
            result[index++] = num % 2;  // 将当前位的二进制值存入数组
            num /= 2;  // 处理下一位
        }
        return result;
    }

    // 递归地将指数表示为符合要求的括号表达式
    private static String representPower(int exp) {
        if (exp == 0) {
            return "0";  // 0 次幂直接返回 "0"
        } else if (exp == 1) {
            return "";  // 2^1 直接返回空字符串(因为 2^1 可以直接表示为 2)
        } else {
            int[] binaryExp = decompose(exp);  // 将指数分解为二进制表示
            StringBuilder sb = new StringBuilder();
            // 从高位到低位遍历二进制表示
            for (int i = binaryExp.length - 1; i >= 0; i--) {
                if (binaryExp[i] == 1) {  // 如果当前位为1
                    if (sb.length() > 0) {
                        sb.append("+");  // 添加 "+" 连接符
                    }
                    if (i == 1) {
                        sb.append("2");  // 2^1 用 "2" 表示
                    } else {
                        sb.append("2(").append(representPower(i)).append(")");  // 递归表示更高的次幂
                    }
                }
            }
            return sb.toString();
        }
    }

    // 主函数,负责处理输入的整数并输出结果
    private static String powerRepresentation(int n) {
        int[] binaryN = decompose(n);  // 将整数 n 分解为二进制表示
        StringBuilder result = new StringBuilder();
        // 从高位到低位遍历二进制表示
        for (int i = binaryN.length - 1; i >= 0; i--) {
            if (binaryN[i] == 1) {  // 如果当前位为1
                if (result.length() > 0) {
                    result.append("+");  // 添加 "+" 连接符
                }
                if (i == 1) {
                    result.append("2");  // 2^1 用 "2" 表示
                } else {
                    result.append("2(").append(representPower(i)).append(")");  // 递归表示更高的次幂
                }
            }
        }
        return result.toString();
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();  // 从标准输入读取整数 n
        System.out.println(powerRepresentation(n));  // 输出符合约定的 n 的表示形式
    }
}

 P1228 地毯填补问题

解题思路

基本思路是使用递归分治策略来解决地毯填补问题。通过不断将大棋盘划分为四个更小的子棋盘,在每个子棋盘中递归地放置地毯,最终实现整个棋盘的填补。具体步骤如下:

  • 递归终止条件:当棋盘大小为 2x2 时,直接返回,因为在这个基本情况下无需进一步划分。
  • 递归分治:在每个递归步骤中,将当前棋盘划分为四个子棋盘,并根据公主的位置决定如何放置中心的 L 形地毯,然后递归处理每个子棋盘。
  • 确定地毯的放置:根据公主所在的位置,将中心位置的三个格子放置一个 L 形地毯,避开公主所在的位置。
import java.util.Scanner;

public class Main {
    /**
     * 递归地解决地毯填补问题
     * @param x1 公主所在行
     * @param y1 公主所在列
     * @param x2 当前子棋盘的左上角行
     * @param y2 当前子棋盘的左上角列
     * @param n 当前子棋盘的大小
     */
    public static void solve(int x1, int y1, int x2, int y2, int n) {
        // 基本情况:当棋盘大小为2x2时,直接返回
        if (n == 1) return;

        // 判断公主所在的位置并放置中心的L形地毯

        // 公主在上半部分
        if (x1 - x2 < (n >> 1)) {
            // 公主在左上部分
            if (y1 - y2 < (n >> 1)) {
                // 放置L形地毯,中心位置在右下
                System.out.println((x2 + (n >> 1)) + " " + (y2 + (n >> 1)) + " " + 1);

                // 递归处理四个子棋盘
                solve(x1, y1, x2, y2, (n >> 1));
                solve(x2 + (n >> 1) - 1, y2 + (n >> 1), x2, y2 + (n >> 1), (n >> 1));
            } else { // 公主在右上部分
                // 放置L形地毯,中心位置在左下
                System.out.println((x2 + (n >> 1)) + " " + (y2 + (n >> 1) - 1) + " " + 2);

                // 递归处理四个子棋盘
                solve(x2 + (n >> 1) - 1, y2 + (n >> 1) - 1, x2, y2, (n >> 1));
                solve(x1, y1, x2, y2 + (n >> 1), (n >> 1));
            }
            // 公共处理部分,属于递归处理四个子棋盘的代码
            solve(x2 + (n >> 1), y2 + (n >> 1) - 1, x2 + (n >> 1), y2, (n >> 1));
            solve(x2 + (n >> 1), y2 + (n >> 1), x2 + (n >> 1), y2 + (n >> 1), (n >> 1));
        } else { // 公主在下半部分
            // 公主在左下部分
            if (y1 - y2 < (n >> 1)) {
                // 放置L形地毯,中心位置在右上
                System.out.println((x2 + (n >> 1) - 1) + " " + (y2 + (n >> 1)) + " " + 3);

                // 递归处理四个子棋盘
                solve(x2 + (n >> 1) - 1, y2 + (n >> 1) - 1, x2, y2, (n >> 1));
                solve(x2 + (n >> 1) - 1, y2 + (n >> 1), x2, y2 + (n >> 1), (n >> 1));
                solve(x1, y1, x2 + (n >> 1), y2, (n >> 1));
                solve(x2 + (n >> 1), y2 + (n >> 1), x2 + (n >> 1), y2 + (n >> 1), (n >> 1));
            } else { // 公主在右下部分
                // 放置L形地毯,中心位置在左上
                System.out.println((x2 + (n >> 1) - 1) + " " + (y2 + (n >> 1) - 1) + " " + 4);

                // 递归处理四个子棋盘
                solve(x2 + (n >> 1) - 1, y2 + (n >> 1) - 1, x2, y2, (n >> 1));
                solve(x2 + (n >> 1) - 1, y2 + (n >> 1), x2, y2 + (n >> 1), (n >> 1));
                solve(x2 + (n >> 1), y2 + (n >> 1) - 1, x2 + (n >> 1), y2, (n >> 1));
                solve(x1, y1, x2 + (n >> 1), y2 + (n >> 1), (n >> 1));
            }
        }
    }

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int k = input.nextInt(); // 读取k值,计算棋盘大小
        int x = input.nextInt(); // 读取公主所在行
        int y = input.nextInt(); // 读取公主所在列
        solve(x, y, 1, 1, 1 << k); // 调用solve方法,开始递归填补棋盘
    }
}

P1498 南蛮图腾

解题思路 

杨辉三角        

  • 使用二进制和异或操作生成特定图案。
  • 控制打印前导空格,使图案居中。
  • 根据行的奇偶性选择不同的打印方式,形成最终图案。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt(); // 读取 n 值
        int size = 1 << n; // 计算 2^n
        int[] a = new int[1030]; // 数组 a 用来存储当前行的状态
        a[0] = 1; // 初始化第一个元素为 1

        for (int i = 0; i < size; ++i) {
            // 打印前导空格
            for (int j = 1; j < size - i; ++j) {
                System.out.print(" ");
            }
            // 修改数组 a
            for (int j = i; j >= 0; --j) {
                if (j > 0) {
                    a[j] ^= a[j - 1];
                }
            }
            // 打印当前行
            if (i % 2 == 0) { // 偶数行
                for (int j = 0; j <= i; ++j) {
                    if (a[j] == 1) {
                        System.out.print("/\\");
                    } else {
                        System.out.print("  ");
                    }
                }
            } else { // 奇数行
                for (int j = 0; j <= i; j += 2) {
                    if (a[j] == 1) {
                        System.out.print("/__\\");
                    } else {
                        System.out.print("    ");
                    }
                }
            }
            System.out.println(); // 换行
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HeShen.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值