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\)条直线最多能分割出的平面个数为\(S_n\)

公式推导:

递推公式:\(S_n=S_{n-1}+n\)

由累加法得:\(S_n-S_0=1+2+...+n\)

\(S_0=1\)及等差数列求和公式得:\(S_n=\dfrac{(1+n)\times n}{2}+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\),从初始状态到目标状态的最少步数为\(S_n\)

公式推导:

递推公式:\(S_n=2\times S_{n-1}+1\)

等比数列构造:\(S_n+1=2\times (S_{n-1}+1)\)

即:\(\dfrac{S_n+1}{S_{n-1}+1}=2\)

由累乘法得:\(\dfrac{S_n+1}{S_0+1}=2^n\)

\(S_0=0\)得:\(S_n=2^n+1\)

观察数据范围\(n\in[0,10^{18}]\),使用快速幂及long类型存储,使用int类型读入long大小的数会显示RE

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

\(a\times b\ \% MOD=(a\ \% MOD)\times(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)\)

则其主对角线的坐标可表示为 \((x-t,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背包 的题目时,误打误撞找到一个骗人的题目,疯狂折磨出题人

递归枚举所有方案

所有的方案个数为 \(nums\le2^{1000000}\)

理想情况下,最多只能拿到测试点编号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\)个物品的总收益记为\(ans_x\),前\(X-1\)个物品的总收益记为\(ans_{x-1}\)

以下关注\(ans_x\)的性质

设每种方案的总体积为\(k_i\),其中 \(i\epsilon[1,2^x]\)

\(ans_x=\sum_{i=1}^{2^x}p^{k_i}\)

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

则总收益变为\(\sum_{i=1}^{2^x}p^{k_i+V}=p^{V}\times\sum_{i=1}^{2^x}p^{k_i}=p^{V}\times ans_x\)

所以,若每种方案的体积都增加\(V\),相当于乘以\(p^{V}\)

考虑第\(X\)个物品的 \(ans_x\)

每种物品仅有两种情况

  1. 不选该物品,由于任意一种方案的物品总体积不变,则对\(ans_{x-1}\)无影响
  2. 选择该物品,对任意一种方案的物品总体积增加\(V_x\),则\(ans_{x-1}\times p^{V_x}\)

\(ans_x\)为这两种情况的价值的和

\(ans_x=ans_{x-1}+ans_{x-1}\times p^{V_x}=ans_{x-1}\times(1+p^{V_x})\)

\(\therefore \dfrac{ans_x}{ans_{x-1}}=1+p^{V_x}\)

由累乘法得:\(\dfrac{ans_x}{ans_0}=\prod_{i=1}^x(1+p^{V_i})\)

\(ans_0\)表示当前无任何物品,即\(V=0\)

\(\therefore ans_0=p^V=p^0=1\)

\(\therefore ans_x=\prod_{i=1}^x(1+p^{V_i})\)

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\) 用符号表示为 \(b\mid a\)

  2. \(1\sim n\) 中约数(即因子)含 \(x\) 的数的个数 为 \(\left\lfloor\dfrac{n}{x}\right\rfloor\)

证明

对于 \(x\) 作为因子,\(x\) 一定为 \(x,2x,3x,4x,...,kx\) 的因子,则 \(k=\left\lfloor\dfrac{n}{x}\right\rfloor\)

所以 \(answer=k=\left\lfloor\dfrac{n}{x}\right\rfloor\)

解决该题

\(n=6\) 为例,即求 \(\sum_{i=1}^6f(i)\)

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

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

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

证明:

\[由题得,要求的值为\sum_{i=1}^{n}f(i)=\sum_{i=1}^{n}\sum_{d\mid i}1 \]

\[即\sum_{i=1}^{n}f(i)=\sum_{i=1}^{n}\sum_{d=1}^{n}{(d\mid i)} \]

\[交换求和顺序\sum_{i=1}^{n}f(i)=\sum_{d=1}^{n}\sum_{i=1}^{n}{(d\mid i)} \]

\[由前置知识2得 \sum_{i=1}^{n}f(i)=\sum_{d=1}^{n}\left\lfloor\dfrac{n}{d}\right\rfloor \]

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(x-1)+f(x-2)+...+f(x-k)\)

递归写法

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

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\times 10^9\)

而题目最后三个测试点的数据范围为\(1\leq a,b,c\leq 100000\),即\(1\times 10^5\)

若三个数互质,则三个接近\(10^5\)的数相乘,会超过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(n-1,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 @ 2022-11-20 22:58  Cattle_Horse  阅读(38)  评论(0编辑  收藏  举报