试题 A: 门牌制作
[题解]
public class Main {
public static void main(String[] args) {
int ans = 0;
for (int i = 1; i <= 2020; i++) {
String s = i + "";
for (int j = 0; j < s.length(); j++) {
if (s.charAt(j) == '2') ans++;
}
}
System.out.println(ans);
}
}
[答案]
624
试题 B: 寻找 2020
[题解]
直接暴力枚举所有可能性
给定的 txt 文件是个 300 * 300 的矩阵
import java.util.Scanner;
public class Main {
public static final int N = 310;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[][] num = new int[N][N];
for (int i = 1; i <= 300; i++) {
String str = sc.next();
for (int j = 1; j <= str.length(); j++) {
num[i][j] = str.charAt(j - 1) - '0';
}
}
int ans = 0;
for (int i = 1; i <= 300; i++) {
for (int j = 1; j <= 300; j++) {
if (i + 3 <= 300 && num[i][j] == 2 && num[i + 1][j] == 0 && num[i + 2][j] == 2 && num[i + 3][j] == 0)
ans++;
}
}
for (int i = 1; i <= 300; i++) {
for (int j = 1; j <= 300; j++) {
if (j + 3 <= 300 && num[i][j] == 2 && num[i][j + 1] == 0 && num[i][j + 2] == 2 && num[i][j + 3] == 0)
ans++;
}
}
for (int i = 1; i <= 300; i++) {
for (int j = 1; j <= 300; j++) {
if (i + 3 <= 300 && j + 3 <= 300 && num[i][j] == 2 && num[i + 1][j + 1] == 0 && num[i + 2][j + 2] == 2 && num[i + 3][j + 3] == 0)
ans++;
}
}
System.out.println(ans);
}
}
[答案]
16520
试题 C: 蛇形填数
[题解]
多写几行会发现第 i 行 i 列中的数 = 第 i - 1 行 i - 1 列 + 4 * (i - 1)
public class Main {
public static void main(String[] args) {
int ans = 1;
for (int i = 0; i < 20; i++) {
ans += 4 * i;
}
System.out.println(ans);
}
}
[答案]
761
试题 D: 七段码
[题解]
import java.util.*;
public class Main {
public static void main(String[] args) {
new Main().go();
}
static final int N = 8;
//存储图, 0 表示没有连接
int[][] G = new int[N][N];
private void go() {
G[1][1] = G[1][2] = G[1][6] = 1;
G[2][2] = G[2][1] = G[2][3] = G[2][7] = 1;
G[3][3] = G[3][2] = G[3][4] = G[3][7] = 1;
G[4][4] = G[4][3] = G[4][5] = 1;
G[5][5] = G[5][4] = G[5][6] = G[5][7] = 1;
G[6][6] = G[6][1] = G[6][5] = G[6][7] = 1;
G[7][7] = G[7][2] = G[7][3] = G[7][5] = G[7][6] = 1;
for (int i = 1; i <= 7; i++) {
for (int j = 1; j <= 7; j++) {
cnt = 0;
String str = "" + i;
dfs(i, j, str);
}
}
System.out.println(set.size());
}
int cnt;
// 存放实际亮灯情况
Set<String> set = new HashSet<>();
private void dfs(int s, int d, String str) {
if (s == d) {
char[] chars = str.toCharArray();
Arrays.sort(chars);
String s1 = new String(chars);
set.add(s1);
return;
}
for (int i = 1; i <= 7; i++) {
// 可行继续向下寻找路径
if (G[s][i] != 0 && !str.contains("" + (char)(i + '0'))) {
str = str + i;
dfs(i, d, str);
str = str.substring(0,str.length() - 1);
}
}
}
}
[答案]
80
试题 E: 排序
[题解]
由于冒泡排序最糟糕的情况是,逆序。
所以为了字符串最短就要使得冒泡处于最糟糕的情况。
最糟糕冒泡排序交换次数 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1), n 为字符串长度
代入得 n = 15 时,需要交换 105 次, n = 14,需要交换 91 次。
则可确定字符串最短长度为 15。
为了使得字典序最短我们取前 15 个字母组成最糟糕冒泡排序的字符串:“onmlkjihgfedcba”, 我们还需交换字母顺序,减少 5 次交换。
由于字典序是从第一个字符开始比较,我们只要使得第一个字符字典序最小即可。
故我们只要将 ‘j’ 移到最前面,其他保持不变,则符合要求。
[答案]
jonmlkihgfedcba
试题 F: 成绩分析
[题解]
import java.io.*;
import java.util.*;
import java.math.*;
import java.util.stream.*;
/**
* 2020 成绩分析 调用 Java 大数 api 防止数据精度问题导致题目错误
*/
public class Main {
public static void main(String[] args) throws Exception {
new Main().go();
}
static BufferedReader br;
static StreamTokenizer st;
static PrintWriter pw;
private void go() throws Exception {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StreamTokenizer(br);
pw = new PrintWriter(new OutputStreamWriter(System.out));
getAns();
pw.flush();
}
private int nextInt() throws Exception {
st.nextToken();
return (int) st.nval;
}
static final int N = 10010;
int n;
private void getAns() throws Exception {
n = nextInt();
int max = 0;
int min = 100;
int sum = 0;
for (int i = 1; i <= n; i++) {
int score = nextInt();
max = Math.max(max, score);
min = Math.min(min, score);
sum += score;
}
pw.println(max);
pw.println(min);
BigDecimal bigDecimalSum = new BigDecimal(sum);
BigDecimal bigDecimalN = new BigDecimal(n);
BigDecimal bigDecimalAvg = bigDecimalSum.divide(bigDecimalN, 2, RoundingMode.HALF_UP);
pw.println(bigDecimalAvg);
}
}
试题 G: 单词分析
[题解]
import java.io.*;
import java.util.*;
import java.math.*;
import java.util.stream.*;
/**
* 2020 单词分析 哈希
*/
public class Main {
public static void main(String[] args) throws Exception {
new Main().go();
}
static BufferedReader br;
static StreamTokenizer st;
static PrintWriter pw;
private void go() throws Exception {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StreamTokenizer(br);
pw = new PrintWriter(new OutputStreamWriter(System.out));
getAns();
pw.flush();
}
private int nextInt() throws Exception {
st.nextToken();
return (int) st.nval;
}
static final int N = 1010;
char[] str;
int[] cnt = new int[26];
private void getAns() throws Exception {
String s = br.readLine();
str = s.toCharArray();
for(char c : str) {
cnt[c - 'a']++;
}
int max = 0;
for(int i : cnt) max = Math.max(max, i);
for (int i = 0; i < cnt.length; i++) {
if (max == cnt[i]) {
pw.println((char)('a' + i));
pw.println(max);
break;
}
}
}
}
试题 H: 数字三角形
[题解]
import java.io.*;
import java.util.*;
import java.math.*;
import java.util.stream.*;
/**
* 2020 数字三角形 dp
* 向左下走的次数与向右下走的次数相差不能超过 1,
* 只是说明最终取得这个点所有左下次数累和与右下次数累和之差不超过 1,
* 而不是每次走都要与上一次不一样。
* 最后能走到中间的,则代表左下次数与右下次数之差不超过 1。
*/
public class Main {
public static void main(String[] args) throws Exception {
new Main().go();
}
static BufferedReader br;
static StreamTokenizer st;
static PrintWriter pw;
private void go() throws Exception {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StreamTokenizer(br);
pw = new PrintWriter(new OutputStreamWriter(System.out));
getAns();
pw.flush();
}
private int nextInt() throws Exception {
st.nextToken();
return (int) st.nval;
}
static final int N = 110;
int[][] a = new int[N][N];
int n;
private void getAns() throws Exception {
n = nextInt();
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
a[i][j] = nextInt();
}
}
dp();
int ans = ((n & 1) == 1 ? f[n][n / 2 + 1] : Math.max(f[n][n / 2], f[n][n / 2 + 1]));
pw.println(ans);
}
// f(i,j,k) i, j 代表几行几列
int[][] f = new int[N][N];
private void dp() {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
f[i][j] = a[i][j] + Math.max(f[i - 1][j - 1], f[i - 1][j]);
}
}
}
}
试题 I: 子串分值和
[题解]
import java.io.*;
import java.util.*;
import java.math.*;
import java.util.stream.*;
/**
* 2020 子串分值和 dp 贡献法
* 定义状态 f(i), 以 i 下标结尾子串的分值和
* 采用贡献法的方式计算状态转移
* 每新增一个字符,必然会产生一个只包含本身的子串故肯定产生 1 个贡献值
* 其次,每新增一个字符对之前 i - 1 个字符包含新增字符的子串产生至多 1 个贡献
* 当 i - 1 个的字符中没有新增字符相同的字符则会产生 1 个贡献
* 我们记与新增字符相同的字符最近位置为 pos, 若不存在则记 pos = 0
* f(i) = f(i - 1) + i - pos - 1 + 1 = f(i - 1) + i - pos
*/
public class Main {
public static void main(String[] args) throws Exception {
new Main().go();
}
static BufferedReader br;
static StreamTokenizer st;
static PrintWriter pw;
private void go() throws Exception {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StreamTokenizer(br);
pw = new PrintWriter(new OutputStreamWriter(System.out));
getAns();
pw.flush();
}
private int nextInt() throws Exception {
st.nextToken();
return (int) st.nval;
}
static final int N = (int)1e5 + 10;
char[] str;
long[] f = new long[N];
int[] pos = new int[26];
private void getAns() throws Exception {
String s = br.readLine();
int n = s.length();
str = s.toCharArray();
long ans = 0;
for (int i = 1; i <= n; i++) {
f[i] = f[i - 1] + i - pos[str[i - 1] - 'a'];
pos[str[i - 1] - 'a'] = i;
ans += f[i];
}
pw.println(ans);
}
}
极致贡献法, 相当于对之前算法的空间进行优化
import java.io.*;
import java.util.*;
import java.math.*;
import java.util.stream.*;
/**
* 2020 子串分值和 贡献法
* 每新增一个字符,必然会产生一个只包含本身的子串故肯定产生 1 个贡献值
* 其次,每新增一个字符对之前 i - 1 个字符包含新增字符的子串产生至多 1 个贡献
* 当 i - 1 个的字符中没有新增字符相同的字符则会产生 1 个贡献
* 我们记与新增字符相同的字符最近位置为 prePos, 若不存在则记 prePos = 0
* 我们对后面 n - i + 1 个字符(包含新增字符)都会产生 i - prePos 的贡献
* 所有每新增一个字符对结果产生的总贡献为 (i - prePos) * (n - i + 1)
*/
public class Main {
public static void main(String[] args) throws Exception {
new Main().go();
}
static BufferedReader br;
static StreamTokenizer st;
static PrintWriter pw;
private void go() throws Exception {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StreamTokenizer(br);
pw = new PrintWriter(new OutputStreamWriter(System.out));
getAns();
pw.flush();
}
static final int N = (int)1e5 + 10;
char[] str;
// 起始不存在位置为 0
int[] prePos = new int[26];
private void getAns() throws Exception {
String s = br.readLine();
int n = s.length();
s = " " + s;
str = s.toCharArray();
long ans = 0;
for (int i = 1; i <= n; i++) {
int index = str[i] - 'a';
ans += (long)(i - prePos[index]) * (n + 1 - i);
prePos[index] = i;
}
pw.println(ans);
}
}
试题 J: 装饰珠
[题解]
import java.io.*;
import java.util.*;
/**
* 2020 装饰珠 dp
* 分析题意,装备数是固定的且实际相关的也只是装饰孔,故我们只要存放装饰孔的数据即可
* 即问题可变换为经典的分组背包问题
* 记 t 为装饰珠可增加价值最大数量
* 1.题意中一个类别的装饰珠数量可以为任意,但超过 t,就没有意义,即没必要枚举大于 t 的值
* 2.为了能够快速定位到一个类别的装饰珠放的位置,应该使得装饰孔按照等级升序排列
* 3.由于装饰珠镶嵌是有条件的,装饰孔按等级排序可以方便我们不用多次判断是否符合条件,只要判断两个端点的装饰孔是否满足条件即可
* 4.同时大等级的 f(j) 更新可能会用到小等级的 f(j),即应该使得装饰珠也按等级升序排列
*/
public class Main {
public static void main(String[] args) throws Exception {
new Main().go();
}
static BufferedReader br;
static StreamTokenizer st;
static PrintWriter pw;
private void go() throws Exception {
br = new BufferedReader(new InputStreamReader(System.in));
st = new StreamTokenizer(br);
pw = new PrintWriter(new OutputStreamWriter(System.out));
getAns();
pw.flush();
}
private int nextInt() throws Exception {
st.nextToken();
return (int) st.nval;
}
// 每件装备最多装饰孔数量
static final int N = 310;
// 装饰珠最多种类数量
static final int M = (int)1e4 + 10;
// 存放装备装饰孔的情况
int[] a = new int[N];
// 存放装饰珠情况
B[] b = new B[M];
// 状态:最大价值量
long[] f = new long[N];
// 装备孔数量
int n;
// 装饰珠种类数量
int m;
private void getAns() throws Exception {
// 读到装备装饰孔情况
for (int i = 1; i <= 6; i++) {
int tmp = nextInt();
for(int j = 1; j <= tmp; j++) {
n++;
a[n] = nextInt();
}
}
// 读到装饰珠情况
// 装饰珠种类数量
m = nextInt();
for (int i = 1; i <= m; i++) {
// 装饰珠等级
int L = nextInt();
// 装饰珠可增加价值最大数量
int P = nextInt();
int[] w = new int[P + 1];
for(int j = 1; j <= P; j++) {
w[j] = nextInt();
}
b[i] = new B(L, P, w);
}
// 主要 Java api 提供的排序区间是左闭右开的
Arrays.sort(a, 1, n + 1);
Arrays.sort(b,1,m + 1, Comparator.comparing(B::getS));
dp();
pw.println(f[n]);
}
private void dp() {
for(int i = 1; i <= m; i++) {
for (int j = n; j >= 1; j--) {
for (int k = 1; k <= b[i].t; k++) {
// 找到同一等级装饰珠起始位置
int p = j - k + 1;
// 满足可镶嵌条件, 都不满足就表示不镶嵌
if (a[j] >= b[i].s && p >= 1 && a[p] >= b[i].s) {
f[j] = Math.max(f[j], f[p - 1] + b[i].w[k]);
}
}
}
}
}
// 装饰珠相关数据
class B {
int s, t; // s 装饰珠等级,t 装饰珠可增加价值最大数量
int[] w; // 不同装饰珠对应的价值量
public B(int s, int t, int[] w) {
this.s = s;
this.t = t;
this.w = w;
}
public int getS() {
return s;
}
}
}
以上综合各位大佬提供给的思路和我自己的思路,自己写出的题解。