Wust Java Club 2022-2023上半学年中期考核

Wust Java Club 2022-2023上半学年中期考核

前言

提交时的注意事项

  1. 不可写入包名,如package edu.wust
  2. 必须有且只能有一个公有类public class Main,若有其他类,不应给其赋为公有,如class test
  3. 一定要导入使用的包名,如import java.util.Scanner;

OnlineJudge的各种返回

CE:编译错误(黄黄的),一般是类名错误,即不是public class Main,或者未导入所使用的包,如import java.util.Scanner;

RE:运行时错误(紫紫的),一般是数组越界,类型转换错误,栈溢出,写入包名package edu.wust

WA:答案错误(红红的),样例过了不代表这题能过

TLE:运行超时,检查算法时间复杂度,若超出时间限制不多,可进行些许常数优化(如位运算,输入输出优化)

MLE:超出空间限制,一般是数组开的过大,dp问题可以考虑进行维度优化

输入

Scanner在读入量小时效果还行

但在读入量十分大时,显得性能十分低下

这里建议使用BufferedReader或者StreamTokenizer

StreamTokenizer:优点是占用内存少,但是它只存储doubleString类型,若要读入int或者long类型,只能进行强制转换,但是double类型存储多位整数的时候会转化为科学计数法,导致精度降低

更多细节请自行搜索

Barmecide Feast

知识点考察:

  • 数学推导

题目链接:T291123 Barmecide Feast - 洛谷

原题链接:无

想法来源:《具体数学》

思路

题意:直线最多能将平面分割分割成多少部分?

直线分割平面问题(数学归纳法)-CSDN博客

n条直线最多能分割出的平面个数为Sn

公式推导:

递推公式:Sn=Sn1+n

由累加法得:SnS0=1+2+...+n

S0=1及等差数列求和公式得:Sn=(1+n)×n2+1

时间限制卡的比较紧,使用BufferedReader读入

关注数据范围两数相乘超出int范围,使用long类型

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        long n = Long.parseLong(in.readLine());
        System.out.println((n * (n + 1) >> 1) + 1);
    }
}

Tower of Hanoi

知识点考察:

  • 数学推导
  • 快速幂
  • 递推

题目链接:T291125 Tower of Hanoi - 洛谷

原题链接:无

想法来源:《具体数学》

证明并推导汉诺塔(河内之塔)问题公式 博客园

设圆盘总数为n,从初始状态到目标状态的最少步数为Sn

公式推导:

递推公式:Sn=2×Sn1+1

等比数列构造:Sn+1=2×(Sn1+1)

即:Sn+1Sn1+1=2

由累乘法得:Sn+1S0+1=2n

S0=0得:Sn=2n+1

观察数据范围n[0,1018],使用快速幂及long类型存储,使用int类型读入long大小的数会显示RE

乘法运算过程中取模对结果取模 最终结果相同

a×b %MOD=(a %MOD)×(b %MOD) %MOD

import java.util.Scanner;

public class Main {
    static Scanner sc = new Scanner(System.in);
    static final long MOD = 998244353;

    static long powMod(long a, long b) {
        long ret = 1;
        for (; b != 0; b >>= 1, a = a * a % MOD)
            if ((b & 1) == 1) ret = ret * a % MOD;
        return ret;
    }

    public static void main(String[] args) {
        final long n = sc.nextLong();
        //减1后可能为负数,加上一个MOD防止结果为负
        System.out.println((powMod(2, n) - 1 + MOD) % MOD);
    }
}

Maximum Path Sum

知识点考察:

  • 动态规划
  • 记忆化搜索

题目链接: T291913 Maximum Path Sum - 洛谷

原题链接:[P1216 USACO1.5][IOI1994]数字三角形 Number Triangles - 洛谷

想法来源: 第二周作业 018 最大和的路径Ⅰ

这题如果用Scanner读入数据,很容易会MLE,即超出空间限制

从上向下走

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {

    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(in.readLine());
        int[][] a = new int[n + 1][n + 1];
        for (int i = 1; i <= n; ++i) {
            String[] line = in.readLine().split(" ");
            for (int j = 1; j <= i; ++j) {
                a[i][j] = Integer.parseInt(line[j - 1]);
            }
        }
        //dp[i][j]代表 从上向下走 到达i行j列的最大路径和
        int[][] dp = new int[n + 1][n + 1];
        //单独处理第一个值
        dp[1][1] = a[1][1];
        for (int i = 2; i <= n; ++i) {
            //单独处理i行第一个数和最后一个数,防止越界
            //第一个数只能从 上方 走来
            dp[i][1] = a[i][1] + dp[i - 1][1];
            for (int j = 2; j < i; ++j) {
                //第i行j列 只能 从i-1行的j-1列和j列 走来
                dp[i][j] = a[i][j] + Math.max(dp[i - 1][j - 1], dp[i - 1][j]);
            }
            //最后一个数只能从 左上方 走来
            dp[i][i] = a[i][i] + dp[i - 1][i - 1];
        }
        int ans = Integer.MIN_VALUE;
        //我看到有的人是对最后一行dp数组排序
        //而排序的时间复杂度是O( nlog n )
        //不如一重循环 O(n)好
        for (int i = 1; i <= n; ++i) ans = Math.max(ans, dp[n][i]);
        System.out.println(ans);
    }
}

从下向上走

从上向下走从下向上走结果是一致的

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {

    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(in.readLine());
        int[][] a = new int[n + 1][n + 1];
        for (int i = 1; i <= n; ++i) {
            String[] line = in.readLine().split(" ");
            for (int j = 1; j <= i; ++j) {
                a[i][j] = Integer.parseInt(line[j - 1]);
            }
        }
        //dp[i][j]代表 从下向上走 到达i行j列的最大路径和
        int[][] dp = new int[n + 1][n + 1];
        //单独处理最后一行的值
        for (int i = 1; i <= n; ++i) dp[n][i] = a[n][i];
        for (int i = n - 1; i >= 1; --i) {
            for (int j = 1; j <= i; ++j) {
                //第i行j列的值由i+1行的j列和j+1列推得
                dp[i][j] = a[i][j] + Math.max(dp[i + 1][j], dp[i + 1][j + 1]);
            }
        }
        System.out.println(dp[1][1]);
    }
}

Eight Queens Puzzle

知识点考察:

  • DFS+回溯

题目链接:T291914 Eight Queens Puzzle - 洛谷

原题链接:[P1219 USACO1.5]八皇后 Checker Challenge - 洛谷

一个锻炼大家代码逻辑能力的题

Code

这份代码过不了最后一个测试点

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    //map[i][j]表示i行j列是否已经有皇后了
    static boolean[][] map;
    static int[] ans;
    //记录有多少个解
    static int count = 0, n;

    //判断row行col列是否能放皇后
    static boolean check(int row, int col) {
        //判断(row,col)的 ↑ 上方是否有皇后
        for (int i = 1; i <= row; ++i) {
            if (map[i][col]) {
                return false;
            }
        }
        //判断(row,col)的 ↖ 对角线是否有皇后
        for (int i = row - 1, j = col - 1; i >= 1 && j >= 1; --i, --j) {
            //如果有皇后,则说明当前位置不能放皇后
            if (map[i][j]) {
                return false;
            }
        }
        //判断(row,col) ↗ 的对角线是否有皇后
        for (int i = row - 1, j = col + 1; i >= 1 && j <= n; --i, ++j) {
            //如果有皇后,则说明当前位置不能放皇后
            if (map[i][j]) {
                return false;
            }
        }
        //如果 ↖ ↑ ↗ 三个方向
        return true;
    }

    //寻找第row行的皇后
    static void dfs(int row) {
        //如果已经超出棋盘,则说明找到了一个可行解
        if (row > n) {
            ++count;
            //如果是前三个解,则输出
            if (count <= 3) {
                for (int i = 1; i <= n; ++i) System.out.print(ans[i] + " ");
                System.out.println();
            }
            return;
        }
        //判断row行i列能不能放皇后
        for (int i = 1; i <= n; ++i) {
            //如果(row,j)能放皇后,则继续向下搜索
            if (check(row, i)) {
                ans[row] = i;
                //标记这个地方放了皇后
                map[row][i] = true;
                //搜索下一行
                dfs(row + 1);
                //回溯,撤回标记
                map[row][i] = false;
            }
        }
    }

    public static void main(String[] args)throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(in.readLine());
        map = new boolean[n + 1][n + 1];
        ans = new int[n + 1];
        dfs(1);
        System.out.println(count);
    }
}

优化

原理:

  • 一个位置的主对角线↙↗的横纵坐标之和相同

  • 一个位置的次对角线↖↘的横纵坐标之差相同

设一个点的坐标为 (x,y)

则其主对角线的坐标可表示为 (xt,y+t)

副对角线的坐标可表示为 (x+t,y+t)

根据这个性质,可以使用一个一维数组来标记某一个对角线是否存在皇后

竖直向上的位置同样可以用一个一维数组来标记

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    static boolean[] upperLeft;
    static boolean[] upperRight;
    static boolean[] top;
    static int[] ans;
    //记录有多少个解
    static int count = 0, n;

    //判断row行col列是否能放皇后
    static boolean check(int row, int col) {
        if (top[col]) return false;
        if (upperLeft[row - col + n]) return false;
        if (upperRight[row + col]) return false;
        return true;
    }

    //寻找第row行的皇后
    static void dfs(int row) {
        //如果已经超出棋盘,则说明找到了一个可行解
        if (row > n) {
            ++count;
            //如果是前三个解,则输出
            if (count <= 3) {
                for (int i = 1; i <= n; ++i) System.out.print(ans[i] + " ");
                System.out.println();
            }
            return;
        }
        //判断row行i列能不能放皇后
        for (int i = 1; i <= n; ++i) {
            //如果(row,j)能放皇后,则继续向下搜索
            if (check(row, i)) {
                ans[row] = i;
                //标记这个地方放了皇后
                top[i] = true;
                upperLeft[row - i + n] = true;
                upperRight[row + i] = true;
                //搜索下一行
                dfs(row + 1);
                //回溯,撤回标记
                top[i] = false;
                upperLeft[row - i + n] = false;
                upperRight[row + i] = false;
            }
        }
    }

    public static void main(String[] args) throws Exception {
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        n = Integer.parseInt(in.readLine());
        top = new boolean[n + 1];
        // 横纵坐标之差的范围在(-n,n)
        // 由于数组无负数下标,给其加上一个偏移量保证它一定在整数范围内
        upperLeft = new boolean[2 * n + 1];
        //横纵坐标之和的范围在[2,2n]
        upperRight = new boolean[2 * n + 1];
        ans = new int[n + 1];
        dfs(1);
        System.out.println(count);
    }
}

Whimsical 0-1 Knapsack Product Problem

知识点考察:

  • 数学推导

题目链接:T291909 Whimsical 0-1 Knapsack Product Problem - 洛谷

原题链接:P7223 01 背包 - 洛谷

想法来源:找 01背包 的题目时,误打误撞找到一个骗人的题目,疯狂折磨出题人

递归枚举所有方案

所有的方案个数为 nums21000000

理想情况下,最多只能拿到测试点编号2~5的分数

注意要对MOD使用finnal关键字

java中final 与效率_fa1d1的博客

import java.util.Scanner;

public class Main {
    static Scanner sc = new Scanner(System.in);
    static int n;
    static int p;
    static int[] w;
    static long ans = 0;
    static final int MOD = 998244353;//一定要用final关键字!!!
    public static void main(String[] args) {
        n = sc.nextInt();
        p = sc.nextInt();
        w = new int[n];
        for (int i = 0; i < n; ++i) {
            w[i] = sc.nextInt();
        }
        dfs(0,0);
        System.out.println(ans);
    }
    static void dfs(int len, long sum) {
        if (len == n) {
            ans = (ans + pow(p, sum)) % MOD;
            return;
        }
        dfs(len + 1, sum);//如果不选择当前物品
        dfs(len + 1, sum + w[len]);//如果选择当前物品
    }
    static long pow(long x, long n) {//尽量使用位运算优化
        long ans = 1;
        for (; n != 0; n >>= 1, x = x * x % MOD) {
            if ((n & 1) == 1) ans = ans * x % MOD;
        }
        return ans;
    }
}

数学推导

X个物品的总收益记为ansx,前X1个物品的总收益记为ansx1

以下关注ansx的性质

设每种方案的总体积为ki,其中 iϵ[1,2x]

ansx=i=12xpki

对于所有方案都增加V体积

则总收益变为i=12xpki+V=pV×i=12xpki=pV×ansx

所以,若每种方案的体积都增加V,相当于乘以pV

考虑第X个物品的 ansx

每种物品仅有两种情况

  1. 不选该物品,由于任意一种方案的物品总体积不变,则对ansx1无影响
  2. 选择该物品,对任意一种方案的物品总体积增加Vx,则ansx1×pVx

ansx为这两种情况的价值的和

ansx=ansx1+ansx1×pVx=ansx1×(1+pVx)

ansxansx1=1+pVx

由累乘法得:ansxans0=i=1x(1+pVi)

ans0表示当前无任何物品,即V=0

ans0=pV=p0=1

ansx=i=1x(1+pVi)

import java.util.Scanner;

public class Main {
    static Scanner sc = new Scanner(System.in);
    static final long MOD = 998244353;
    static int n;
    static long ans = 1l, p;
    static int[] V;

    static long powMod(long a, int b) {
        long ret = 1;
        for (; b != 0; b >>= 1, a = a * a % MOD)
            if ((b & 1) == 1) ret = ret * a % MOD;
        return ret;
    }

    public static void main(String[] args) {
        n = sc.nextInt();
        p = sc.nextLong();
        V = new int[n];
        for (int i = 0; i < n; ++i) V[i] = sc.nextInt();
        for (int v : V) ans = ans * (1l + powMod(p, v)) % MOD;
        System.out.println(ans);
    }
}

Multiple

知识点考察:

  • 简单数论
  • 数学推导

题目链接:T291922 Multiple - 洛谷

原题链接:P1403 约数研究 - 洛谷

想法来源: 第一周题目 001 3或5的倍数

不可思议,纯暴力居然能拿80分,数据还得加强

思路

前置知识

  1. a 能整除 b 用符号表示为 ba

  2. 1n 中约数(即因子)含 x 的数的个数 为 nx

证明

对于 x 作为因子,x 一定为 x,2x3x4x...kx 的因子,则 k=nx

所以 answer=k=nx

解决该题

n=6 为例,即求 i=16f(i)

1 的约数:1
2 的约数:12
3 的约数:1, ,3
4 的约数:12, ,4
5 的约数:1, , , ,5
6 的约数:123, , ,6

正常思路:求每个数的约数个数,再求和,即横着看

换种思路:求 1n 中有多少个数约数含 i ,其中 i[1,n],即竖着看

证明:

i=1nf(i)=i=1ndi1

i=1nf(i)=i=1nd=1n(di)

i=1nf(i)=d=1ni=1n(di)

2i=1nf(i)=d=1nnd

Code

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int ans = 0;
        for (int i = 1; i <= n; ++i) {
            ans += n / i;
        }
        System.out.println(ans);
    }
}

Climbing Stairs

知识点考察:

  • 线性动态规划
  • 递推
  • 递归+记忆化数组

题目链接:T291917 Climbing Stairs - 洛谷

原题链接:P1192 台阶问题 - 洛谷

想法来源: 第四周题目 031 货币面值的组合问题

思路

设到达第x级台阶的方案数为f(x)

由于最多能迈K级台阶,所以f(x)=f(x1)+f(x2)+...+f(xk)

递归写法

与斐波那契数列递归写法相同,但是数据量很大,重复计算的数据更多,所以需要记忆化数组进行剪枝优化

import java.util.Scanner;

public class Main {
    static int[] memory;
    static int MOD = 100003;
    static int N, k;

    static int getAns(int n) {
        //如果这一级台阶的步骤数不为初值0,则说明已经计算过了
        if (memory[n] != 0) return memory[n];
        int ans = 0;
        //最小台阶为0,取max防止负数台阶出现
        for (int i = Math.max(0, n - k); i < n; ++i) {
            ans = (ans + getAns(i)) % MOD;
        }
        //返回该台阶方案数之前,先赋值给记忆化数组
        return memory[n] = ans;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        N = sc.nextInt();
        k = sc.nextInt();
        memory = new int[N + 1];
        //底部为0号台阶,不需要走,算一种方案
        //1号台阶同理
        memory[0] = memory[1] = 1;
        System.out.println(getAns(N));
    }
}

线性递推

与记忆化递归写法相同,只是求解顺序不同而已

import java.util.Scanner;

public class Main {
    static final int MOD = 100003;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(), k = sc.nextInt();
        //dp[i]代表到达第i级台阶的方案数
        int[] dp = new int[n + 1];
        //同理,0级与1级台阶方案数为1
        dp[0] = dp[1] = 1;
        for (int i = 2; i <= n; ++i) {
            //dp[i]=dp[i-1]+dp[i-2]+...+dp[i-k]
            for (int j = Math.max(0, i - k); j < i; ++j) {
                dp[i] += dp[j];
                dp[i] %= MOD;
            }
        }
        System.out.println(dp[n]);
    }
}

Permutation

知识点考察:

  • DFS+回溯

题目链接:T291923 Permutation - 洛谷

原题链接:P1706 全排列问题 - 洛谷

不要使用System.out.printf("%5d",i);String.format("%5d",i)固定位宽格式化输出,会MLE

DFS回溯法求全排列

对其访问过的点打上访问标记,在遍历时跳过已打过标记的点,以确保 每个点仅访问一次

Code

import java.util.Scanner;

public class Main {
    static Scanner sc = new Scanner(System.in);
    //book[i]代表i已经被选择过了
    static boolean[] book;
    //存储当前的排列
    static int[] num;
    static int N;

    static void dfs(int n) {
        //已经搜索到最后一个数了
        if (n > N) {
            for (int i = 1; i <= N; ++i) {
                System.out.print("    " + num[i]);
            }
            System.out.println();
        }
        //遍历所有数
        for (int i = 1; i <= N; ++i) {
            //如果i已经被选择,则跳过
            if (book[i]) continue;
            //给i这个数打上标记,代表选上i
            book[i] = true;
            //将i存入当前排列答案中
            num[n] = i;
            //继续搜索下一个数
            dfs(n + 1);
            //撤回标记
            book[i] = false;
        }
    }

    public static void main(String[] args) {
        N = sc.nextInt();
        num = new int[N + 1];
        book = new boolean[N + 1];
        dfs(1);
    }
}

下一个排列

下一个排列的定义是:

给定数字序列的字典序中下一个更大的排列。如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

对于非严格递减的序列没有下一个排列

简单来说就是,下一个数比当前数大且尽可能的小

推导

举个例子,求7562541的下一个排列

对于一个序列

越靠前的数对整个序列的影响幅度越大,越靠后的数对整个序列的影响幅度越小

对于非严格递减的数没有下一个排列

所以要找到一个靠后有下一个排列的最小单元序列,也就是最后一个相邻升序元素对

我们发现(2,5)是最后一个相邻的升序的元素对,因此,2541是满足条件的最小单元序列(这个子序列从第二个元素开始,一定是非严格递减的)

我们需要让这个子序列2541大一点点,于是在2541的后缀541中从后向前找第一个大于首元素的数字,也就是4,让它与第一个元素2进行交换

这样得到的就是以4为首的最大排列4521,对其后缀521进行逆序操作,就能得到以4为首的最小排列4125

因此得到了7562541的下一个排列7564125

描述

下一个排列的描述:

  1. 从后向前查找第一个相邻的 升序 的元素对(代表这个后缀有下一个排列),即找到最后一个arr[i]<arr[i+1]。(此时[i+1,end)是非严格递减的)
  2. 在后缀[i+1,end)中找到最小大于arr[i]的数,记为arr[k]。由于后缀[i+1,end)是非严格递减的,所以从后向前找到第一个大于arr[i]的数即可
  3. 交换arr[i]arr[k]。这时得到的后缀是以arr[k]为首的最大的排列
  4. 对后缀[i+1,end)进行逆序操作,来得到以arr[k]为首的最小的排列。
  5. 若在步骤1中无符合条件的相邻的元素对,则说明此时数组是非严格降序的,没有下一个排列了

Code

import java.util.Scanner;

public class Main {
    public static void swap(int[] arr, int x, int y) {
        arr[x] ^= arr[y] ^ (arr[y] = arr[x]);
    }

    //作用:生成下一个排列 (数组范围[l,r])
    public static boolean nextPermutation(int[] arr, int l, int r) {
        if (arr.length <= 1) return false;
        int i = r - 1;
        while (i >= l && !(arr[i] < arr[i + 1])) --i;
        if (i == l - 1) return false;
        int k = r;
        while (arr[i] >= arr[k]) --k;
        swap(arr, i, k);
        for (int left = i + 1, right = r; left < right; ++left, --right) swap(arr, left, right);
        return true;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] arr = new int[n + 1];
        for (int i = 1; i <= n; ++i) arr[i] = i;
        do {
            for (int i = 1; i <= n; ++i) System.out.print("    " + arr[i]);
            System.out.println();
        } while (nextPermutation(arr, 1, n));
    }
}

Exclusive or Lamp

知识点考察:

  • 异或性质

题目链接:T291916 Exclusive or Lamp - 洛谷

原题链接:P1161 开灯 - 洛谷

想法来源:136. 只出现一次的数字 - 力扣

本来想考察异或的,结果用暴力的数组判断也能过 T_T

思路

因为只剩下一个编号是开着灯的,所以对所有编号异或,剩下的值就是开着的灯

Code

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        double t1;
        int t2, ans = 0;
        for (int i = 1; i <= n; ++i) {
            t1 = sc.nextDouble();
            t2 = sc.nextInt();
            for (int j = 1; j <= t2; ++j) {
                ans ^= (int) (t1 * j);
            }
        }
        System.out.println(ans);
    }
}

Euclidean‘s Jogging

知识点考察:

  • 最大公约数gcd
  • 最小公倍数lcm

题目链接:T291918 Euclidean‘s Jogging - 洛谷

原题链接:P4057 晨跑 - 洛谷

想法来源: 第一周题目 005 最小公倍数

思路

题目很长,但是最后仅要求三个数的最小公倍数

注意数据范围呀!!!

很多人85分就是因为没注意数据范围

int的最大值大约为2×109

而题目最后三个测试点的数据范围为1a,b,c100000,即1×105

若三个数互质,则三个接近105的数相乘,会超过int范围

需要使用long类型

Code

import java.util.Scanner;

public class Main {
    static long gcd(long a, long b) {
        return b == 0 ? a : gcd(b, a % b);
    }

    static long lcm(long a, long b) {
        return a / gcd(a, b) * b;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        long a = sc.nextLong(), b = sc.nextLong(), c = sc.nextLong();
        System.out.println(lcm(a, lcm(b, c)));
    }
}

0-1 Knapsack Problem

知识点考察:

  • 01背包

题目链接:T291919 0-1 Knapsack Problem - 洛谷

原题链接:P1049 装箱问题 - 洛谷

01背包变式,体积就是价值

dp数组可定义为:

  1. 最小剩余空间
  2. 所用的最大空间,最后用V减去它

最小剩余空间

dp[i][j]代表 前i个物品有j个空间的背包 目前所剩的最少空间

以这种思路未写正确的,是未理解dp数组的含义

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int V = in.nextInt();
        int N = in.nextInt();
        int[] weight = new int[N + 1];
        int[][] dp = new int[N + 1][V + 1];
        for (int i = 1; i <= N; ++i) {
            weight[i] = in.nextInt();
        }
        //初始化所有背包都剩V体积
        for (int i = 0; i <= N; ++i) {
            Arrays.fill(dp[i], V);
        }
        for (int i = 1; i <= N; ++i) {
            for (int j = 0; j <= V; ++j) {
                //如果背包放得下,且目前剩余空间也放得下
                if (j >= weight[i] && dp[i - 1][j - weight[i]] >= weight[i]) {
                    dp[i][j] = Math.min(dp[i - 1][j], dp[i - 1][j - weight[i]] - weight[i]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        System.out.println(dp[N][V]);
    }
}

所用的最大空间

dp[i][j]代表前i个物品有j体积的背包,目前能使用的最大体积

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int V = in.nextInt();
        int N = in.nextInt();
        int[] weight = new int[N + 1];
        int[][] dp = new int[N + 1][V + 1];
        for (int i = 1; i <= N; ++i) {
            weight[i] = in.nextInt();
        }
        for (int i = 1; i <= N; ++i) {
            for (int j = 0; j <= V; ++j) {
                if (j >= weight[i]) {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + weight[i]);
                } else {
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        System.out.println(V - dp[N][V]);
    }
}

空间优化

以下以对第二种方法进行优化作为示例

二维

观察dp数组,只与前一行有关,可使用滚动数组取模进行优化

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int V = in.nextInt();
        int N = in.nextInt();
        int[] weight = new int[N + 1];
        int[][] dp = new int[N + 1][V + 1];
        for (int i = 1; i <= N; ++i) {
            weight[i] = in.nextInt();
        }
        for (int i = 1; i <= N; ++i) {
            for (int j = 0; j <= V; ++j) {
                if (j >= weight[i]) {
                    //取模运算,只使用两行数组
                    dp[i % 2][j] = Math.max(dp[(i - 1) % 2][j], dp[(i - 1) % 2][j - weight[i]] + weight[i]);
                } else {
                    dp[i % 2][j] = dp[(i - 1) % 2][j];
                }
            }
        }
        //输出时同样需要取模
        System.out.println(V - dp[N % 2][V]);
    }
}

一维

01背包问题及一维数组优化 CSDN博客

import java.util.Scanner;

public class Main {
    static Scanner sc = new Scanner(System.in);

    public static void main(String[] args) {
        int V = sc.nextInt(), n = sc.nextInt();
        int[] v = new int[n + 1], dp = new int[V + 1];
        for (int i = 1; i <= n; ++i) v[i] = sc.nextInt();
        for (int i = 1; i <= n; ++i) {
            for (int j = V; j >= v[i]; --j) {
                dp[j] = Math.max(dp[j], dp[j - v[i]] + v[i]);
            }
        }
        System.out.println(V - dp[V]);
    }
}

Countdown to Death

知识点考察:

  • 递推
  • 数学推导

题目链接:T291920 Countdown to Death - 洛谷

原题链接:P8671 蓝桥杯 2018 国 AC 约瑟夫环 - 洛谷

想法来源:《具体数学》

思路

约瑟夫环的数学公式推导 - CSDN博客

设总人数为n,报数终点为k,最后胜利的人的编号为S(n,k)

递推公式:S(n,k)=(S(n1,k)+k)%n

当最后只剩下一个人时,编号应为0

因为取模后,编号是从0开始的,所以结果需要加1

递归-栈溢出

使用递归的写法会因为栈溢出显示RE

import java.util.Scanner;

public class Main {
    static int get(int n, int k) {
        if (n == 1) return 0;
        return (get(n - 1, k) + k) % n;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(), k = sc.nextInt();
        System.out.println(get(n, k) + 1);
    }
}

递推

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt(), k = sc.nextInt();
        int ans = 0;
        for (int i = 1; i <= n; ++i) {
            ans = (ans + k) % i;
        }
        System.out.println(ans + 1);
    }
}
posted @   Cattle_Horse  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示