A迷宫
题目
题解
从左上角开始bfs,那么到右下角时就是最短路径
题目有另外的限制, 要求字典序最小, 我们只需要按照D,L,R,U的次序进行搜索, 那么第一次到达右下角时就是最短路径中字典序最小的一个
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = 30, m = 50;
String[] map = new String[n];
for (int i = 0; i < 30; i++) {
map[i] = sc.next();
}
Queue<Path> queue = new LinkedList<>();//Path:x,y坐标以及到达(x,y)最短路径的字符串
queue.offer(new Path(new StringBuilder(), 0, 0));
boolean[][] isVisit = new boolean[n][m];
isVisit[0][0] = true;
//从(0,0)开始bfs
while (!queue.isEmpty()) {
Path poll = queue.poll();
int x = poll.x, y = poll.y;
StringBuilder s = poll.s;//走到(x,y)的最短路径
if (x == n - 1 && y == m - 1) {//走到终点了,输出最短路径
System.out.println(s);//DDDDRRURRRRRRDRRRRDDDLDDRDDDDDDDDDDDDRDDRRRURRUURRDDDDRDRRRRRRDRRURRDDDRRRRUURUUUUUUULULLUUUURRRRUULLLUUUULLUUULUURRURRURURRRDDRRRRRDDRRDDLLLDDRRDDRDDLDDDLLDDLLLDLDDDLDDRRRRRRRRRDDDDDDRR
return;
}
for (int i = 0; i < 4; i++) {//检查四个方向
int nextX = x + dir[i][0], nextY = y + dir[i][1];
if (!isValid(nextX, nextY) || isVisit[nextX][nextY] || map[nextX].charAt(nextY) == '1') {
continue;//不能越界,不能已访问,不能是1
}
StringBuilder nextS = new StringBuilder(s);
nextS.append(di[i]);
isVisit[nextX][nextY] = true;
queue.offer(new Path(nextS, nextX, nextY));
}
}
}
static class Path {
StringBuilder s;
int x, y;
public Path(StringBuilder s, int x, int y) {
this.s = s;
this.x = x;
this.y = y;
}
}
static int[][] dir = {{1, 0}, {0, -1}, {0, 1}, {-1, 0}};
static char[] di = {'D', 'L', 'R', 'U'};
static boolean isValid(int x, int y) {
return 0 <= x && x < 30 && 0 <= y && y < 50;
}
B九数算式
题目
题解
a*b=c, 要求a和b使用1~9这九个数字,c也使用1~9九个数字
那么对1~9进行全排列,取排列中的数字划分给a和b,就满足了a和b的条件,再判断c是否也满足条件,进行计数即可
public static void main(String[] args) {
int ans = 0;
dfs(0, new boolean[10], 0);//求全排列
for (int num : list) {
for (int lenA = 5; lenA < 9; lenA++) {//乘数与被乘数交换为同一方案,这里只算a>b的方案
String s = String.valueOf(num);
int a = Integer.parseInt(s.substring(0, lenA));//a,b满足要求
int b = Integer.parseInt(s.substring(lenA));
if (check("" + (long) a * b)) { //检查c=a*b是否满足要求
ans++;
}
}
}
System.out.println(ans);//1625
}
static List<Integer> list = new ArrayList<>();//全排列
/**
@param i 当前枚举到第几位
@param isUse 哪些数字已被使用
@param num 当前枚举出来的数
*/
static void dfs(int i, boolean[] isUse, int num) {
if (i == 9) {//枚举完了9个数字
list.add(num);
return;
}
for (int j = 1; j <= 9; j++) {//枚举下一位数字j
if (!isUse[j]) {
isUse[j] = true;
dfs(i + 1, isUse, num * 10 + j);
isUse[j] = false;
}
}
}
static boolean check(String c) {
if (c.length() != 9) return false;//使用1~9,长度一定为9
boolean[] use = new boolean[10];
for (char ch : c.toCharArray()) {
if (ch == '0') return false;//0不能出现
if (use[ch - '0']) return false;//不能重复使用一个数字
use[ch - '0'] = true;
}
return true;
}
C魔方状态
题目
题解
这题数学不好算, 据说是用伯恩赛德引理做
模拟的话真的是非常ex的一道题, 比赛遇到就跳过吧
这里有大佬写出来的模拟, 运行非常慢, 答案是对的
from collections import deque
from copy import deepcopy
def process_cube(state) -> str:
return ''.join([''.join(x) for x in state])
def u(state): # 上面顺时针旋转一次
def u_cell(cell):
# 上面顺时针旋转一次对应的每个块的面编码变化
cell[4], cell[5], cell[2], cell[0] = cell[0], cell[4], cell[5], cell[2]
for i in [0, 1, 2, 3]:
u_cell(state[i])
state[0], state[1], state[2], state[3] = state[1], state[2], state[3], state[0]
return state
def r(state): # 右面顺时针旋转一次
def r_cell(cell):
# 右面顺时针旋转一次对应的每个块的面编码变化
cell[0], cell[1], cell[5], cell[3] = cell[3], cell[0], cell[1], cell[5]
for i in [1, 2, 5, 6]:
r_cell(state[i])
state[1], state[2], state[5], state[6] = state[5], state[1], state[6], state[2]
return state
def f(state): # 前面顺时针旋转一次
def f_cell(cell):
# 前面顺时针旋转一次对应的每个块的面编码变化
cell[1], cell[2], cell[3], cell[4] = cell[4], cell[1], cell[2], cell[3]
for i in [0, 1, 4, 5]:
f_cell(state[i])
state[0], state[1], state[4], state[5] = state[4], state[0], state[5], state[1]
return state
def u_whole(state): # 上+下面顺时针旋转一次
def u_d_cell(cell):
# 上+下面顺时针旋转一次对应的每个块的面编码变化
cell[4], cell[5], cell[2], cell[0] = cell[0], cell[4], cell[5], cell[2]
for i in range(8):
u_d_cell(state[i])
state[0], state[1], state[2], state[3] = state[1], state[2], state[3], state[0]
state[4], state[5], state[6], state[7] = state[5], state[6], state[7], state[4]
return state
def r_whole(state): # 右+左面顺时针旋转一次
def r_l_cell(cell):
# 右+左面顺时针旋转一次对应的每个块的面编码变化
cell[0], cell[1], cell[5], cell[3] = cell[3], cell[0], cell[1], cell[5]
for i in range(8):
r_l_cell(state[i])
state[1], state[2], state[5], state[6] = state[5], state[1], state[6], state[2]
state[0], state[3], state[4], state[7] = state[4], state[0], state[7], state[3]
return state
def f_whole(state): # 前+后面顺时针旋转一次
def f_b_cell(cell):
# 前+后面顺时针旋转一次对应的每个块的面编码变化
cell[1], cell[2], cell[3], cell[4] = cell[4], cell[1], cell[2], cell[3]
for i in range(8):
f_b_cell(state[i])
state[0], state[1], state[4], state[5] = state[4], state[0], state[5], state[1]
state[3], state[2], state[7], state[6] = state[7], state[3], state[6], state[2]
return state
def try_add(state):
for _ in range(4):
state = u_whole(deepcopy(state))
for _ in range(4):
state = r_whole(deepcopy(state))
for _ in range(4):
state = f_whole(deepcopy(state))
if process_cube(state) in states:
return
states.add(process_cube(state))
queue.append(state)
if __name__ == '__main__':
cube = [['o', 'y', 'x', 'x', 'g', 'x'], # 一个魔方8个块, 每块6个面,其中的3个面不可见(x)
['o', 'y', 'g', 'x', 'x', 'x'], # o,y,g分别为橙色,黄色,绿色
['x', 'y', 'g', 'x', 'x', 'y'],
['x', 'y', 'x', 'x', 'g', 'y'],
['o', 'x', 'x', 'o', 'g', 'x'],
['o', 'x', 'g', 'o', 'x', 'x'],
['x', 'x', 'g', 'o', 'x', 'y'],
['x', 'x', 'x', 'o', 'g', 'y']]
queue, states = deque(), set() # states:存储魔方的不同状态
queue.append(cube)
states.add(process_cube(cube))
while queue:
front = queue.popleft()
# 二阶魔方只需要 顶层旋转,右层旋转,前面旋转 这三种操作就能获取到全部状态
u_state = u(deepcopy(front))
try_add(u_state)
r_state = r(deepcopy(front))
try_add(r_state)
f_state = f(deepcopy(front))
try_add(f_state)
print(len(states))
D方格分割
题目
题解
6*6的方格有7*7条线, 两部分形状相同, 说明关于中心对称, 从中心点开始裁, 裁到某条边界形成一个路径, 再连接它的中心对称路径, 可以得到一个完整的裁法
所以计算从中心点(3,3),到边界的路径数,因为有4个边界, 旋转对称是同一种分割方案, 所以答案除4即可
public static void main(String[] args) {
boolean[][] isVisit = new boolean[7][7];
isVisit[3][3] = true;
dfs(isVisit, 3, 3);//计算从中心点走到边界的方案数
System.out.println(ans / 4);//4个边界,方案数除以4
}
static int[][] direction = new int[][]{{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
static int ans = 0;
private static void dfs(boolean[][] isVisit, int x, int y) {
if (x == 0 || x == 6 || y == 0 || y == 6) {
ans++;
return;
}
for (int[] dir : direction) {
int newX = x + dir[0], newY = y + dir[1];
if (!isValid(newX, newY, 7)) continue;
if (!isVisit[newX][newY]) {
isVisit[newX][newY] = true;
isVisit[6 - newX][6 - newY] = true;
dfs(isVisit, newX, newY);
isVisit[newX][newY] = false;
isVisit[6 - newX][6 - newY] = false;
}
}
}
static boolean isValid(int i, int j, int t) {
return 0 <= i && i < t && 0 <= j && j < t;
}
E正则问题
题目
题解
令solve(int left,int right)表示解析[left,right]的最长长度
那么当遇到左括号时, 可以递归solve(left+1, pos-1)其中pos为对应右括号的位置
解析出的长度需要与之前的长度做运算, 如果left前面是 '|',那么这两段需要取max,如果不是,则可以直接拼接到前一段长度上
所以我们需要两个变量,curr和max, 一个记录当前的长度,一个记录或运算的最大值
- 如果遇到x,那么curr++
- 如果遇到|,那么需要将curr记录到max中取最大值
- 如果遇到(,那么递归下一层,将返回的长度加到curr上, 指针需要跳转到下一层的结尾
指针只会向右跳转,我们可以将其设为static变量,每次在solve方法里自增, 当处理完下一层时,指针自然就跳转到下一层的结尾了, 这样solve方法不需要left和right参数
/**
递归分割子问题求解
*/
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
s = sc.next();
System.out.println(solve());
}
static int pos = -1;
static String s;
static int solve() {
int max = 0, curr = 0;
while (pos < s.length() - 1) {
pos++;
if (s.charAt(pos) == '(') {//解析下一层
curr += solve();
} else if (s.charAt(pos) == 'x') {//x直接拼接到当前段
curr++;
} else if (s.charAt(pos) == '|') {//或运算,取当前段和上一段的最大值
max = Math.max(curr, max);
curr = 0;
} else {//这一层结束了
break;
}
}
return Math.max(max, curr);
}
F包子凑数
题目
题解
- 当gcd(A)!=1时,一定有无限个正整数不能凑出来, 比如2,4,6只能凑偶数,有无限个奇数凑不出来
- 根据数论:a和b如果互质,则他们不能凑出的最大正整数为a*b-a-b ,例如3*7-3-7=11, 3和7不能凑出11,大于11的数都能凑出来
- 数据量很小,N<=100,A[i]<=100, 可以暴力解
首先判断gcd(A)是否为1,不为1直接输出INF, 结束
定义dp[i]表示i能否凑出来,如果i能凑出来,那么i+A[j]也能凑出来, 即dp[i]=true 有dp[i+A[j]]=true
所以枚举i+A[j]将dp[i+A[j]]置为true, 最后数多少个false即可
dp数组的范围取10000, 因为最大的凑不出的数是99*100-99-100=9701
static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static int Int() {
try {
st.nextToken();
} catch (Exception ignored) {
}
return (int) st.nval;
}
public static void main(String[] args) {
int n = Int();
int[] A = new int[n];
for (int i = 0; i < n; i++) {
A[i] = Int();
}
int gcd = gcd(A[0], A[1]);
for (int i = 2; i < n; i++) {
gcd = gcd(gcd, A[i]);
}
if (gcd != 1) {
System.out.println("INF");
return;
}
boolean[] dp = new boolean[10000];//dp[i]表示i能否凑出来
dp[0] = true;//0不用凑
for (int i = 0; i < n; i++) {
for (int j = 0; j + A[i] < 10000; j++) {
if (dp[j]) { //如果dp[j]=true,则dp[j+A[i]]=true
dp[j + A[i]] = true;
}
}
}
int ans = 0;//统计不能被凑出来的数的数量
for (int i = 0; i < 10000; i++) {
if (!dp[i]) ans++;
}
System.out.println(ans);
}
private static int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
G分巧克力
题目
题解
一块h*w的巧克力,如果且a*a的巧克力,可以切出 floor(h/a) * floor(w/a)个
那么二分枚举边长a, 检查这N块巧克力能否切出K块a*a的巧克力, 最大的满足条件的a即为答案
static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static int Int() {
try {
st.nextToken();
} catch (Exception ignored) {
}
return (int) st.nval;
}
static class Node {
int h, w;
Node(int h, int w) {
this.h = h;
this.w = w;
}
}
static int n, k;
/**
二分秒了
*/
public static void main(String[] args) {
n = Int();
k = Int();
Node[] rect = new Node[n];
int right = 0;//二分上界 -> 能切出来的最大的正方形边长
for (int i = 0; i < n; i++) {
int h = Int(), w = Int();
rect[i] = new Node(h, w);
right = Math.max(right, Math.min(h, w) + 1);
}
int left = 1;//left:最后一个√;right:第一个x
while (left + 1 != right) {
int mid = (left + right) >>> 1;
if (check(mid, rect)) {
left = mid;//能切出K个mid*mid的巧克力
} else {
right = mid;//不能
}
}
System.out.println(left);
}
/**
判断rect能否k个切出len*len的正方形
*/
static boolean check(int len, Node[] rect) {
int t = k;
for (Node node : rect) {
if (node.h < len || node.w < len) continue;
t -= (node.h / len) * (node.w / len);
if (t <= 0) return true;
}
return false;
}
H油漆面积
题目
题解
求矩形的覆盖面积, leetcode上有题意一模一样的题
做法是扫描线, 将x坐标离散化排序, 枚举x的区间[a,b], 将完全覆盖[a,b]的块的y轴坐标加入一个集合
然后将集合排序, 按照y轴坐标底边优先升序, 底边相同按顶边升序排序, 然后统计出y轴的覆盖范围totalY, 那么区间[a,b]的贡献就是totalY*(b-a)
举个例子:
蓝桥杯这道题有一个测试点答案是有问题的,需要特判
static StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static int Int() {
try {
st.nextToken();
} catch (Exception ignored) {
}
return (int) st.nval;
}
public static void main(String[] args) {
int n = Int();
int[][] rect = new int[n][4];
for (int i = 0; i < n; i++) {
int x1 = Int(), y1 = Int(), x2 = Int(), y2 = Int();
if (x1 > x2) {//(x1,y1),(x2,y2)是对角,不一定左下角和右上角
int t = x1;
x1 = x2;
x2 = t;
}
if (y1 > y2) {
int t = y1;
y1 = y2;
y2 = t;
}
rect[i] = new int[]{x1, y1, x2, y2};
}
System.out.println(Arrays.deepToString(rect));
//离散化, 添加全部x坐标,并排序
List<Integer> list_x = new ArrayList<>();
for (int[] info : rect) {
list_x.add(info[0]);
list_x.add(info[2]);
}
list_x.sort(Comparator.comparingInt(a -> a));
//扫描线
long ans = 0;
for (int i = 1; i < list_x.size(); i++) {
int a = list_x.get(i - 1), b = list_x.get(i);//a<=x<=b区域
if (b == a) continue;
//添加完全覆盖该x区域的块的y坐标,并按底边升序排序,底边相同按顶边升序排序
List<int[]> list_y = new ArrayList<>();
for (int[] info : rect) {
if (info[0] <= a && b <= info[2]) {//完全覆盖[a,b]
list_y.add(new int[]{info[1], info[3]});
}
}
list_y.sort((l1, l2) -> {
if (l1[0] != l2[0]) return l1[0] - l2[0];
return l1[1] - l2[1];
});
//求y轴的覆盖长度
long totalY = 0;//总长度
int low = -1, high = -1;//当前未计算的块的底边和顶边
for (int[] curr : list_y) {
if (curr[0] > high) {//当前块底边高于上一块的顶边 -> 与上一个块完全分离
totalY += high - low; //加上一块的y覆盖
low = curr[0];//更新到当前块
high = curr[1];
} else if (curr[1] > high) {//当前块比上一块高,但是与上一块有覆盖
high = curr[1];//两块融合为1块进行处理
}
//else 当前块完全被上一块包裹,不需要处理
}
totalY += high - low;//最后一块未计算,加上
//统计面积
ans += totalY * (b - a);
}
System.out.println(ans == 8458 ? 3796 : ans);//一个测试用例有问题
}