【蓝桥日记①】2017第八届省赛(软件类)JavaA组❤️答案解析
文章目录
- 【蓝桥日记①】2017第八届省赛(软件类)JavaA组❤️答案解析
- A、迷宫
- B、9数算式
- C、魔方状态
- D、方格分割
- E、字母组串
- F、最大公共子串
- G、正则问题
- H、包子凑数
- I、分巧克力
- J、油漆面积
题目链接:第八届蓝桥杯大赛个人赛省赛(软件类)Java大学A组
官网题库中搜索相应题目
A、迷宫
考点:dfs
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 迷宫
* ***/
public class A {
public static void main(String[] args) {
String[] grids = { "UDDLUULRUL", "UURLLLRRRU", "RRUURLDLRD", "RUDDDDUUUU", "URUDLLRRUU",
"DURLRLDLRL", "ULLURLLRDU", "RDLULLRDDD", "UUDDUDUDLL", "ULRDLUURRR" };
char[][] gs = new char[10][10];
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
gs[i][j] = grids[i].charAt(j);
}
}
int res = 0;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
boolean[] marks = new boolean[100];
if (dfs(gs, i, j, marks)) res++;
}
}
System.out.println(res);
}
private static boolean dfs(char[][] gs, int r, int c, boolean[] marks) {
if (r < 0 || c < 0 || r >= 10 || c >= 10) return true;
if (marks[r * 10 + c]) return false;
marks[r * 10 + c] = true;
if (gs[r][c] == 'U') {
return dfs(gs, r - 1, c, marks);
} else if (gs[r][c] == 'D') {
return dfs(gs, r + 1, c, marks);
} else if (gs[r][c] == 'L') {
return dfs(gs, r, c - 1, marks);
} else {
return dfs(gs, r, c + 1, marks);
}
}
}
答案:
31
B、9数算式
考点:全排列+组合枚举
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 9数算式
* ***/
public class B {
static int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9};
static int res = 0;
public static void main(String[] args) {
backtrack(0);
res /= 2;
System.out.println(res);
}
private static void backtrack(int k) {
if (k == a.length) {
int x1 = 0, x2 = 0, y = 0;
for (int i = 0; i < a.length; i++) {
x1 = x1 * 10 + a[i];
x2 = 0;
for (int j = i + 1; j < a.length; j++) {
x2 = x2 * 10 + a[j];
}
y = x1 * x2;
if (check(y)) res++;
}
return;
}
for (int i = k; i < a.length; i++) {
int t = a[k]; a[k] = a[i]; a[i] = t;
backtrack(k + 1);
t = a[k]; a[k] = a[i]; a[i] = t;
}
}
private static boolean check(int num) {
int cnt = 0;
boolean[] mark = new boolean[10];
while (num > 0) {
int x = num % 10;
if (x == 0 || mark[x] == true) {
return false;
}
mark[x] = true;
cnt++;
num /= 10;
}
return cnt == 9;
}
}
答案:
1625
C、魔方状态
考点:排列组合和群
方法一:队列模拟
将块和面编号,使用队列模拟,每个状态通过如下旋转产生三个状态
- 上层块顺时针旋转
- 右层块顺时针旋转
- 前层块顺时针旋转
新生成的状态通过哈希去重考虑是否加入队列
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 魔方状态
* ***/
/***
* 上层逆时针 0 1 2 3
* 下层逆时针 4 5 6 7
* o橙色 g绿色 y黄色 x无色
* 对于每一个小块 前后上下右左 表示为 0 5 1 3 2 4
* ***/
public class C {
static char[][] start = {
"oyxxgx".toCharArray(),
"oygxxx".toCharArray(),
"xygxxy".toCharArray(),
"xyxxgy".toCharArray(),
"oxxogx".toCharArray(),
"oxgoxx".toCharArray(),
"xxgoxy".toCharArray(),
"xxxogy".toCharArray()
};
// 若是正常二阶魔方,队列q需改为4000000
static char[][][] q = new char[2000000][8][6];
static Set<String> all_state = new HashSet<>();
static int front, tail;
private static String to_string(char[][] s) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 6; j++) {
sb.append(s[i][j]);
}
}
return sb.toString();
}
private static void swap(char[] a, int i, int j) {
char t = a[i];
a[i] = a[j];
a[j] = t;
}
private static void swap(char[][] s, int i, int j) {
char[] t = s[i];
s[i] = s[j];
s[j] = t;
}
// 上层块的旋转,面的相对位置调换
private static void ucell(char[] a) {
swap(a, 0, 2);
swap(a, 2, 5);
swap(a, 5, 4);
}
// 上层顺时针旋转
private static void u(char[][] s) {
ucell(s[0]);
ucell(s[1]);
ucell(s[2]);
ucell(s[3]);
// 块的相对位置调换
swap(s, 0, 1);
swap(s, 1, 2);
swap(s, 2, 3);
}
// 右层块的旋转,面的相对位置调换
private static void rcell(char[] a) {
swap(a, 0, 1);
swap(a, 0, 3);
swap(a, 3, 5);
}
// 右层顺时针旋转
private static void r(char[][] s) {
rcell(s[1]);
rcell(s[2]);
rcell(s[5]);
rcell(s[6]);
// 块的相对位置调换
swap(s, 1, 2);
swap(s, 1, 5);
swap(s, 5, 6);
}
// 前层块的旋转,面的相对位置调换
private static void fcell(char[] a) {
swap(a, 1, 2);
swap(a, 1, 4);
swap(a, 3, 4);
}
// 前层顺时针旋转
private static void f(char[][] s) {
fcell(s[0]);
fcell(s[1]);
fcell(s[4]);
fcell(s[5]);
// 块的相对位置调换
swap(s, 0, 1);
swap(s, 0, 4);
swap(s, 4, 5);
}
// 从整个魔方顶部看,顺时针转, 用于判重
private static void uwhole(char[][] s) {
u(s);
// 下层旋转
ucell(s[4]);
ucell(s[5]);
ucell(s[6]);
ucell(s[7]);
swap(s, 4, 5);
swap(s, 5, 6);
swap(s, 6, 7);
}
// 从整个魔方右侧看,顺时针转,用于判重
private static void rwhole(char[][] s) {
r(s);
// 左层旋转
rcell(s[0]);
rcell(s[3]);
rcell(s[4]);
rcell(s[7]);
swap(s, 0, 3);
swap(s, 0, 4);
swap(s, 4, 7);
}
// 从整个魔方前面看,顺时针转,用于判重
private static void fwhole(char[][] s) {
f(s);
// 后层旋转
fcell(s[2]);
fcell(s[3]);
fcell(s[6]);
fcell(s[7]);
swap(s, 3, 2);
swap(s, 3, 7);
swap(s, 6, 7);
}
private static boolean try_insert(char[][] s) {
char[][] cur = new char[8][6];
memcpy(cur, s);
for (int i = 0; i < 4; i++) {
fwhole(cur);
for (int j = 0; j < 4; j++) {
rwhole(cur);
for (int k = 0; k < 4; k++) {
uwhole(cur);
if (all_state.contains(to_string(cur))) {
return false;
}
}
}
}
all_state.add(to_string(s));
return true;
}
private static void memcpy(char[][] news, char[][] s) {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 6; j++) {
news[i][j] = s[i][j];
}
}
}
private static void solve() {
front = 0;
tail = 1;
// 初始化队列的第一个状态
memcpy(q[front], start);
all_state.add(to_string(start));
while (front < tail) {
memcpy(q[tail], q[front]);
f(q[tail]); // 前层顺时针旋转
if (try_insert(q[tail])) tail++;
memcpy(q[tail], q[front]);
u(q[tail]); // 上层顺时针旋转
if (try_insert(q[tail])) tail++;
memcpy(q[tail], q[front]);
r(q[tail]); // 右层顺时针旋转
if (try_insert(q[tail])) tail++;
front++;
// System.out.println(front + "\t" + tail);
}
System.out.println(front);
}
public static void main(String[] args) {
solve();
}
}
答案:
229878
没有什么是算法(数学)解决不了的,可以模拟算一下正规二阶魔方的状态数,类比考虑下三阶魔方的代码怎么写,状态数有多少?
方法二:数学定理burnside
这个涉及到数学排列组合和群的知识,看不懂 (⊙ˍ⊙)
补一个Python解法:
from functools import reduce
def f(n):
return reduce(lambda x, y: x * y, range(1, n + 1))
if __name__ == '__main__':
s1 = f(8) * 3 ** 8 / 16
s2 = 3 * f(4) * 3 ** 4
s3 = 6 * f(4) * 3 ** 4
print(int(((s1 + s2 + s3) // 24) // 3))
参考资料
2017 第八届蓝桥杯 魔方状态
2017/Province_Java_B/4/魔方状态
D、方格分割
考点:构造转化+dfs
原来是6个格子,现在我们从中心点出发(此时有7x7个点),将中心(3, 3)看为原点坐标,分别向原点对称方向游走,遇到边界则构造了一个对称分割的方格。
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 方格分割
* ***/
public class D {
static int[] dirs = new int[]{-1, 0, 1, 0, -1};
static int res = 0;
static boolean[][] vis = new boolean[7][7];
public static void main(String[] args) {
dfs(3, 3);
res /= 4;
System.out.println(res);
}
private static void dfs(int x, int y) {
if (x == 0 || y == 0 || x == 6 || y == 6) {
res += 1;
return;
}
vis[x][y] = vis[6 - x][6 - y] = true;
for (int i = 0; i < 4; i++) {
int newX = x + dirs[i];
int newY = y + dirs[i + 1];
if (newX < 0 || newY < 0 || newX > 6 || newY > 6) continue;
if (!vis[newX][newY]) {
dfs(newX, newY);
}
}
vis[x][y] = vis[6 - x][6 - y] = false;
}
}
答案:
509
E、字母组串
考点:递归
很简单的一道题,却展现了递归的魅力!
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 字母组串
* ***/
public class E {
public static void main(String[] args) {
System.out.println(f(1, 1, 1, 2));
System.out.println(f(1, 2, 3, 3));
}
private static int f(int a, int b, int c, int n) {
if (a < 0 || b < 0 || c < 0) return 0;
if (n == 0) return 1;
return f(a - 1, b, c, n - 1) + f(a, b - 1, c, n - 1) + f(a, b, c - 1, n - 1);
}
}
答案:
f(a - 1, b, c, n - 1) + f(a, b - 1, c, n - 1) + f(a, b, c - 1, n - 1)
F、最大公共子串
考点:动态规划 最长公共子序列问题
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 最大公共子串
* ***/
public class F {
public static void main(String[] args) {
int n = f("abcdkkk", "baabcdadabc");
System.out.println(n);
}
private static int f(String s1, String s2) {
char[] c1 = s1.toCharArray();
char[] c2 = s2.toCharArray();
int[][] a = new int[c1.length + 1][c2.length + 1];
int max = 0;
for (int i = 1; i < a.length; i++) {
for (int j = 1; j < a[i].length; j++) {
if (c1[i - 1] == c2[j - 1]) {
a[i][j] = 1 + a[i - 1][j - 1];
if (a[i][j] > max) max = a[i][j];
}
}
}
return max;
}
}
答案:
1 + a[i - 1][j - 1]
G、正则问题
考点:嵌套循环的递归
(看视频看晕了(づ。◕‿‿◕。)づ,不过这道题是真的好!!!)
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 正则问题
* ***/
public class G {
static int pos = 0;
public static void main(String[] args) {
// s = "((xx|xxx)x|(xxx))xx"
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
int res = f(s);
System.out.println(res);
sc.close();
}
private static int f(String s) {
int maxLen = 0, t = 0;
while (pos < s.length()) {
if (s.charAt(pos) == '(') {
pos++;
t += f(s);
} else if (s.charAt(pos) == 'x') {
pos++;
t++;
} else if (s.charAt(pos) == '|') {
pos++;
maxLen = Math.max(maxLen, t);
t = 0;
} else if (s.charAt(pos) == ')') {
pos++;
break;
}
}
return Math.max(maxLen, t);
}
}
H、包子凑数
考点:不定方程解
对于方程
a
x
+
b
y
=
C
ax+by=C
ax+by=C
- 若a,b互质,则x,y一定有解且有无穷多个
- 若a,b不互质,使得方程无解的C的个数有限,且 m a x { C ∣ 导致方程无解 } = a ∗ b − a − b max\{C|导致方程无解\} = a*b-a-b max{C∣导致方程无解}=a∗b−a−b
对于方程
a
0
x
0
+
a
1
x
1
+
a
2
x
2
+
⋅
⋅
⋅
+
a
n
x
n
=
C
a_0x_0+a_1x_1+a_2x_2+···+a_nx_n=C
a0x0+a1x1+a2x2+⋅⋅⋅+anxn=C
-
若 ( a 0 , a 1 , a 2 , ⋅ ⋅ ⋅ , a n ) (a_0,a_1,a_2,···,a_n) (a0,a1,a2,⋅⋅⋅,an)互质,则有有限个解C
-
若 ( a 0 , a 1 , a 2 , ⋅ ⋅ ⋅ , a n ) (a_0,a_1,a_2,···,a_n) (a0,a1,a2,⋅⋅⋅,an)不互质,则有无限多个C导致方程无解
凑不成C的个数 -> 完全背包问题
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 包子凑数
* ***/
public class H {
static int n, g;
static int[] a = new int[101];
//根据a,b互质,ax+by=c,无解的个数上界为ab-a-b, 则为 100 * 100 - 100 - 100
static boolean[] mark = new boolean[10000];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
mark[0] = true;
n = sc.nextInt();
for (int i = 0; i < n; i++) {
a[i] = sc.nextInt();
if (i == 0) {
g = a[0];
} else {
g = gcd(g, a[i]);
}
for (int j = 0; j < 10000; j++) {
if (mark[j] && j + a[i] < 10000) {
mark[j + a[i]] = true;
}
}
}
if (g != 1) {
System.out.println("INF");
return;
}
int cnt = 0;
for (boolean b : mark) {
if (!b) cnt++;
}
System.out.println(cnt);
sc.close();
}
private static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
}
I、分巧克力
考点:双指针
双指针左边界为1,右边界为边长的最小值。
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 分巧克力
* ***/
import java.util.*;
/***
* 2017年 JavaA
* 分巧克力
* ***/
public class I {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
int[][] sz = new int[n][2];
for (int i = 0; i < n; i++) {
sz[i][0] = sc.nextInt();
sz[i][1] = sc.nextInt();
}
sc.close();
int res = 0;
int le = 1, ri = 100000;
while (le <= ri) {
int mid = le + (ri - le) / 2;
if (check(sz, k, mid)) {
res = mid;
le = mid + 1;
} else {
ri = mid - 1;
}
}
System.out.println(res);
}
private static boolean check(int[][] sz, int k, int a) {
int cnt = 0;
for (int[] cur : sz) {
cnt += (cur[0] / a) * (cur[1] / a);
}
return cnt >= k;
}
}
J、油漆面积
方法一:暴力法(5/6),其一个是案例错误😒
标记每个矩形覆盖的点
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 油漆面积 : 暴力法
* ***/
public class J2 {
static boolean[][] mark = new boolean[10001][10001];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int a, b, c, d;
for (int k = 0; k < n; k++) {
a = sc.nextInt();
b = sc.nextInt();
c = sc.nextInt();
d = sc.nextInt();
for (int i = a; i < c; i++) {
for (int j = b; j < d; j++) {
mark[i][j] = true;
}
}
}
int area = 0;
for (boolean[] mr : mark) {
for (boolean cur : mr) {
if (cur) area++;
}
}
System.out.println(area);
}
}
方法二:差分数组(0/6)
这个好像超出内存限制了
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 油漆面积
* ***/
public class J {
static int[][] diff = new int[10001][10001]; // 差分数组
static int[][] sum = new int[10001][10001]; // 求和数组
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
for (int i = 0; i < n; i++) {
int x1 = sc.nextInt();
int y1 = sc.nextInt();
int x2 = sc.nextInt();
int y2= sc.nextInt();
diff[x1][y1] += 1;
diff[x1][y2] -= 1;
diff[x2][y1] -= 1; // 加1而不是加2是因为本身右上顶点已是矩形区域的边界
diff[x2][y2] += 1;
}
// 计算差分数组
for (int i = 1; i <= 10000; i++) {
for (int j = 1; j <= 10000; j++) {
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + diff[i][j];
}
}
int area = 0;
for (int[] rsum : sum) {
for (int s : rsum) {
if (s > 0) area += 1;
}
}
System.out.println(area);
}
}
方法三:线段树+扫描线
本菜鸟一年前把线段树学会了,光是线段树数据结构本身就有些难顶了,线段树+扫描线的应用更是难上加难,不容易啊(づ。◕‿‿◕。)づ
package eightSession;
import java.util.*;
/***
* 2017年 JavaA
* 油漆面积 线段树+扫描线
* ***/
public class J3 {
// in 和 out 分别表示 入边和出边
private static final int IN = 1;
private static final int OUT = -1;
private static final int MOD = (int)1e9 + 7;
public static void main(String[] args) {
// 将纵坐标去重用于后续 离散化
Set<Integer> ySet= new TreeSet();
// 建立 实际坐标和离散化坐标的 反向索引
Map<Integer, Integer> y2index = new HashMap();
Map<Integer, Integer> index2y = new HashMap();
// 用于存储扫描线(入边和出边)的信息 int[]{x1, y1, y2, 1|-1}
List<int[]> xList = new ArrayList();
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int x1, y1, x2, y2;
for (int k = 0; k < n; k++) {
x1 = sc.nextInt();
y1 = sc.nextInt();
x2 = sc.nextInt();
y2 = sc.nextInt();
// 入边(1) 和 出边(-1) 信息
xList.add(new int[]{x1, y1, y2, IN});
xList.add(new int[]{x2, y1, y2, OUT});
ySet.add(y1);
ySet.add(y2);
}
// 将坐标离散化,从1开始
int count = 1;
for (int y : ySet) {
y2index.put(y, count);
index2y.put(count, y);
count++;
}
// 将扫描线按照x轴坐标升序排序
// Collections.sort(xList, (a, b) -> a[0] - b[0]);
xList.sort(Comparator.comparingInt(x -> x[0]));
SegmentTree segmentTree = new SegmentTree();
long ans = 0;
for (int i = 0; i < xList.size() - 1; i++) {
int[] cur = xList.get(i);
int left = y2index.get(cur[1]);
int right = y2index.get(cur[2]);
// 将当前的边区间更新到线段树上
segmentTree.update(left, right - 1, cur[3], index2y);
int height = segmentTree.query();
int wide = xList.get(i + 1)[0] - cur[0];
ans += (long)height * wide;
}
System.out.println(ans);
}
}
/*** 离散化 线段树 ***/
class SegmentTree{
private static final int maxNode = 10001;
private TreeNode root;
// 用于扫描线问题的线段树类
private class TreeNode {
TreeNode left;
TreeNode right;
int cover;
int coverLen;
}
public SegmentTree() {
root = new TreeNode();
}
public int query() {
return root.coverLen;
}
public void update(int left, int right, int value, Map<Integer, Integer> index2y) {
update(root, 1, maxNode, left, right, value, index2y);
}
private void update(TreeNode node, int start, int end, int left, int right, int value, Map<Integer, Integer> index2y) {
createNode(node);
if (left <= start && end <= right) {
node.cover += value;
// 更新当前节点的区间长度
pushUp(node, start, end, index2y);
return;
}
int mid = start + (end - start) / 2;
if (left <= mid) update(node.left, start, mid, left, right, value, index2y);
if (mid < right) update(node.right, mid + 1, end, left, right, value, index2y);
// 更新当前节点的区间长度
pushUp(node, start, end, index2y);
}
// 更新线段树节点对应的区间长度
private void pushUp(TreeNode node, int start, int end, Map<Integer, Integer> index2y) {
// root.cover 更新到整个区间上所有边的in或out之和
// 若 node.cover > 0, 则找到实际的覆盖长度
if (node.cover > 0) {
node.coverLen = index2y.get(end + 1) - index2y.get(start);
} else if (start != end) {
// 若该区间没被完全覆盖,则返回左右子树的区间覆盖长度即可
node.coverLen = node.left.coverLen + node.right.coverLen;
} else {
// 若start == end, 则置为0, 对覆盖的区间长度没有贡献
node.coverLen = 0;
}
}
private void createNode(TreeNode node) {
if (node.left == null) node.left = new TreeNode();
if (node.right == null) node.right = new TreeNode();
}
}
参考资料:力扣 矩形面积 II【扫描线+离散化+线段树动态开点】
17年的题算是踏踏实实做完了!总的来说学到了很多,题目越艰难,参考的资料越多,花的时间越多,做出后的成就感就越大。尤其是魔方状态那道题真的让我感受到了算法之美,油漆面积中线段树+扫描线的理解学习真的太痛苦了。加油加油ヾ(◍°∇°◍)ノ゙!