蓝桥杯 2021年省赛真题 (Java 大学A组 )
Placeholder
#A 相乘
本题总分: 5 5 5 分
问题描述
小蓝发现,他将 1 1 1 至 1000000007 1000000007 1000000007 之间的不同的数与 2021 2021 2021 相乘后再求除以 1000000007 1000000007 1000000007 的余数,会得到不同的数。
小蓝想知道,能不能在 1 1 1 至 1000000007 1000000007 1000000007 之间找到一个数,与 2021 2021 2021 相乘后再除以 1000000007 1000000007 1000000007 后的余数为 999999999 999999999 999999999。如果存在,请在答案中提交这个数;
如果不存在,请在答案中提交 0 0 0。
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
17812964
朴素解法
朴素的去枚举 [ 1 , 1000000007 ] [1,1000000007] [1,1000000007] 中的每一个数,看似不明智,但实际上,
对于现代的 C P U \mathrm{CPU} CPU 来说,就是洒洒水。
就算你的 C P U \mathrm{CPU} CPU 主频低至 2.0 G h z 2.0\mathrm{Ghz} 2.0Ghz,那也是每秒钟二十亿次的计算速度。
不要小瞧了现代计算机啊,混蛋。
public class Test {
public static void main(String[] args) {
new Test().run(); }
int N = 1000000007, M = 999999999;
void run() {
for (int i = 1; i <= N; i++)
if (i * 2021L % N == M) System.out.println(i);
}
}
余数定义
余数的定义是,
给定两个整数 a a a、 b b b,其中 b ≠ 0 b \ne 0 b=0,那么一定存在两个唯一的整数 q q q、 r r r,使得 a = q b + r , 0 ≤ r < ∣ b ∣ a=qb+r,0 \leq r < |b| a=qb+r,0≤r<∣b∣
而在这道题中,我们最后要找的,可能存在的这个数字可表示为,
2021 ⋅ a ′ = 1000000007 ⋅ q + 999999999 2021 \cdot a' = 1000000007 \cdot q + 999999999 2021⋅a′=1000000007⋅q+999999999,
显然 q q q 不会超过 2021 2021 2021,
枚举的变量选择为 q q q 的话,就能大大的减少枚举范围。
public class Test {
public static void main(String[] args) {
new Test().run(); }
long N = 1000000007, M = 999999999;
void run() {
for (int i = 1; i < 2021; i++)
if ((i * N + M) % 2021 == 0)
System.out.println((i * N + M) / 2021);
}
}
同余方程
扩展欧几里得算法
依题意,有同余线性方程:
a × x ≡ b ( m o d n ) a × x \equiv b \pmod{n} a×x≡b(modn), gcd ( a , n ) ∣ b \gcd(a,n) \mid b gcd(a,n)∣b
将 2021 2021 2021 代入 a a a, 1000000007 1000000007 1000000007 代入 n n n, gcd ( a , n ) = 1 \gcd(a,n)=1 gcd(a,n)=1,方程有无穷解。
稍微解释一下,
a × x ≡ b ( m o d n ) a × x \equiv b \pmod{n} a×x≡b(modn) 可改写为 a × x + n × y = b a × x + n × y = b a×x+n×y=b,
用扩展欧几里得算法求出一组数 x 0 , y 0 x_{0}, y_{0} x0,y0,使得 a × x 0 + n × y 0 = gcd ( a , n ) a × x_{0} + n × y_{0} = \gcd(a,n) a×x0+n×y0=gcd(a,n),
则 x = b × x 0 gcd ( a , n ) x = \cfrac{b × x_{0}}{\gcd(a,n)} x=gcd(a,n)b×x0 是原方程的一个解。
通解为 b × x 0 gcd ( a , n ) m o d n ‾ ( m o d n ) \overline{\cfrac{b × x_{0}}{\gcd(a,n)} \bmod n} \pmod{n} gcd(a,n)b×x0modn(modn),
人话一点就是模 n n n 与 x x x 同余的同余类。
def exgcd(a, b):
if b == 0:
return (1, 0, a)
(x, y, d) = exgcd(b, a % b)
return (y, x - a // b * y, d);
(x, y, d) = exgcd(2021, 1000000007)
print((x * 999999999 // d) % (1000000007 // d))
毕竟是结果填空题,
就用 P y t h o n \mathrm{Python} Python 写了,
下面再放个 J a v a \mathrm{Java} Java 的吧。
费马小定理
对 a a a, a ∈ Z a \in \mathbb{Z} a∈Z,若 p p p 为质数, gcd ( a , p ) = 1 \gcd (a,p) = 1 gcd(a,p)=1,有
a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod p ap−1≡1(modp)
容易整理出方程,
a x ≡ a p − 1 ( m o d p ) x ≡ a p − 2 ( m o d p ) b x ≡ b a p − 2 ≡ b ( m o d p ) \begin{aligned}ax & \equiv a^{p-1} &\pmod p\\x & \equiv a^{p-2} &\pmod p\\bx & \equiv ba^{p-2} \equiv b &\pmod p\end{aligned} axxbx≡ap−1≡ap−2≡bap−2≡b(modp)(modp)(modp)
public class Test {
public static void main(String[] args) {
new Test().run(); }
int a = 2021, b = 999999999, p = 1000000007;
void run() {
System.out.println(pow(a, p - 2) * b % p);
}
long pow(int a, int n) {
if (n == 1) return a;
long res = pow(a, n >> 1);
res = res * res % p;
return (n & 1) == 1 ? res * a % p : res;
}
}
J a v a \mathrm{Java} Java 实现起来较为容易一点。
#B 直线
本题总分: 5 5 5 分
问题描述
在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一条。
给定平面上 2 × 3 2 × 3 2×3 个整点 { ( x , y ) ∣ 0 ≤ x < 2 , 0 ≤ y < 3 , x ∈ Z , y ∈ Z } \{(x, y)|0 ≤ x < 2, 0 ≤ y < 3, x ∈ Z, y ∈ Z\} {
(x,y)∣0≤x<2,0≤y<3,x∈Z,y∈Z},即横坐标是 0 0 0 到 1 1 1 (包含 0 0 0 和 1 1 1) 之间的整数、纵坐标是 0 0 0 到 2 2 2 (包含 0 0 0 和 2 2 2) 之间的整数的点。这些点一共确定了 11 11 11 条不同的直线。
给定平面上 20 × 21 20 × 21 20×21 个整点 { ( x , y ) ∣ 0 ≤ x < 20 , 0 ≤ y < 21 , x ∈ Z , y ∈ Z } \{(x, y)|0 ≤ x < 20, 0 ≤ y < 21, x ∈ Z, y ∈ Z\} {
(x,y)∣0≤x<20,0≤y<21,x∈Z,y∈Z},即横坐标是 0 0 0 到 19 19 19 (包含 0 0 0 和 19 19 19) 之间的整数、纵坐标是 0 0 0 到 20 20 20 (包含 0 0 0 和 20 20 20) 之间的整数的点。请问这些点一共确定了多少条不同的直线。
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
40257
直线方程集合
一种朴素的想法,是将所有点连接起来,去掉重复的线,然后统计。
为了方便表示,这里采用斜截式方程 y = k x + b y=kx+b y=kx+b 来表示每一条直线,其中 k k k 为直线斜率, b b b 为直线在 y y y 轴上的截距,并统一不处理斜率不存在的线,将结果加上一个 20 20 20。
注意! 这段程序的结果是不准确的。
import java.util.HashSet;
import java.util.Set;
public class Test {
public static void main(String[] args) {
new Test().run(); }
int X = 20, Y = 21;
void run() {
Set<Line> set = new HashSet();
for (int x1 = 0; x1 < X; x1++)
for (int y1 = 0; y1 < Y; y1++)
for (int x2 = x1; x2 < X; x2++)
for (double y2 = 0; y2 < Y; y2++)
if (x1 != x2){
double k = (y2 - y1) / (x2 - x1);
double b = -x1 * k + y1;
set.add(new Line(k, b));
}
System.out.println(set.size() + X);
}
class Line {
double k, b;
Line(double b, double k) {
this.k = k;
this.b = b;
}
@Override
public boolean equals(Object obj) {
return k == ((Line)obj).k && b == ((Line)obj).b;
}
@Override
public int hashCode() {
return (int)k ^ (int)b;
}
}
}
分式消除误差
斜率在浮点数表示下,精度那是参差不齐,诚然可以将误差限制在一个范围内,当绝对差落入当中时,我们就将其视为值相同。
但是对于这种需要可表示的范围小的时候,我们可以定义分式来做到无误差,而不是控制精度。
import java.util.HashSet;
import java.util.Set;
public class Test {
public static void main(String[] args) {
new Test().run(); }
int X = 20, Y = 21;
void run() {
Set<Line> set = new HashSet();
for (int x1 = 0; x1 < X; x1++)
for (int y1 = 0; y1 < Y; y1++)
for (int x2 = x1; x2 < X; x2++)
for (int y2 = 0; y2 < Y; y2++)
if (x1 != x2){
Fraction k = new Fraction(y2 - y1, x2 - x1);
Fraction b = new Fraction(y1 * (x2 - x1) - x1 * (y2 - y1),x2 - x1);
set.add(new Line(k, b));
}
System.out.println(set.size() + X);
}
class Fraction {
int numerator, denominator;
Fraction(int numerator, int denominator) {
int gcd = gcd(numerator, denominator);
this.denominator = denominator /gcd;
this.numerator = numerator / gcd;
}
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b); }
@Override
public boolean equals(Object obj) {
return this.numerator == ((Fraction)obj).numerator && this.denominator == ((Fraction)obj).denominator;
}
}
class Line {
Fraction k, b;
Line(Fraction b, Fraction k) {
this.k = k;
this.b = b;
}
@Override
public boolean equals(Object obj) {
return this.k.equals(((Line)obj).k) && this.b.equals(((Line)obj).b);
}
@Override
public int hashCode() {
return k.denominator;
}
}
}
平面几何
这是一个平面直角坐标系,原点与 ( 1 , 2 ) (1,2) (1,2) 连成一条线段。
我们将经过这两点的直线,以及这条直线经过的点与该点于横竖轴的垂线标记出来。
显然,若直线经过 ( x 1 , y 1 ) (x_{1},y_{1}) (x1,y1)、 ( x 2 , y 2 ) (x_{2},y_{2}) (x2,y2) 两点,那么它必然也经过 ( x 1 + k ( x 1 − x 2 ) , y 1 + k ( y 1 − y 2 ) ) (x_{1} +k(x_{1} - x_{2}),y_{1} + k(y_{1} - y_{2})) (x1+k(x1−x2),y1+k(y1−y2)), k ∈ Z k \in Z k∈Z。
若在连接一条直线时,将所有直线经过的点标记起来,在下次遇到已经标记过的两点,我们便可直接跳过。
public class Test {
public static void main(String[] args) {
new Test().run(); }
int X = 20, Y = 21;
void run() {
int count = 0;
boolean[][][][] marked = new boolean[X][Y][X][Y];
for (int x1 = 0; x1 < X; x1++)
for (int y1 = 0; y1 < Y; y1++) {
marked[x1][y1][x1][y1] = true;
for (int x2 = 0; x2 < X; x2++)
for (int y2 = 0; y2 < Y; y2++) {
if (marked[x1][y1][x2][y2]) continue;
int x = x1, y = y1, xOffset = x - x2, yOffset = y - y2;
while (x >= 0 && x < X && y >= 0 && y < Y) {
x += xOffset;
y += yOffset;
}
x -= xOffset;
y -= yOffset;
while (x >= 0 && x < X && y >= 0 && y < Y) {
for (int i = x - xOffset, j = y - yOffset; i >= 0 && i < X && j >= 0 && j < Y; i -= xOffset, j -= yOffset) {
marked[x][y][i][j] = marked[i][j][x][y] = true;
}
x -= xOffset;
y -= yOffset;
}
count++;
}
}
System.out.println(count);
}
}
我觉得可能会再考个差不多的,这里给大伙一个推论。
平面直角坐标系上有 n × n n × n n×n, n ≥ 2 n \ge 2 n≥2 个点 { ( x , y ) ∣ 0 ≤ x < n , 0 ≤ y < n , x ∈ Z , y ∈ Z } \{(x, y)|0 ≤ x < n, 0 ≤ y < n, x ∈ Z, y ∈ Z\} { (x,y)∣0≤x<n,0≤y<n,x∈Z,y∈Z},从原点出发可连接的不同直线为 1 ≤ x , y < n 1 \leq x,y <n 1≤x,y<n, x ≠ y x \ne y x=y 中 g c d ( x , y ) = 1 gcd(x,y) = 1 gcd(x,y)=1 的次数加 3 3 3。
感兴趣的读者可以自行证明。
同时在 1 ≤ x < y < n 1 \leq x < y < n 1≤x<y<n 时, g c d ( x , y ) = 1 gcd(x,y) = 1 gcd(x,y)=1 的出现次数恰好等于 ∑ y = 2 n − 1 φ ( y ) \displaystyle\sum_{y = 2}^{n-1}\varphi(y) y=2∑n−1φ(y),其中 φ \varphi φ 为欧拉函数。
能力有限,这里便不再继续讨论。
#C 货物摆放
本题总分: 10 10 10 分
问题描述
小蓝有一个超大的仓库,可以摆放很多货物。
现在,小蓝有 n n n 箱货物要摆放在仓库,每箱货物都是规则的正方体。小蓝规定了长、宽、高三个互相垂直的方向,每箱货物的边都必须严格平行于长、宽、高。
小蓝希望所有的货物最终摆成一个大的立方体。即在长、宽、高的方向上分别堆 L 、 W 、 H L、W、H L、W、H 的货物,满足 n = L × W × H n = L × W × H n=L×W×H。
给定 n n n,请问有多少种堆放货物的方案满足要求。
例如,当 n = 4 n = 4 n=4 时,有以下 6 6 6 种方案: 1 × 1 × 4 1×1×4 1×1×4、 1 × 2 × 2 1×2×2 1×2×2、 1 × 4 × 1 1×4×1 1×4×1、 2 × 1 × 2 2×1×2 2×1×2、 2 × 2 × 1 2 × 2 × 1 2×2×1、 4 × 1 × 1 4 × 1 × 1 4×1×1。
请问,当 n = 2021041820210418 n = 2021041820210418 n=2021041820210418 (注意有 16 16 16 位数字)时,总共有多少种方案?
提示:建议使用计算机编程解决问题。
答案提交
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
2430
暴力搜索
每届必考的基本算术定理。
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
new Test().run(); }
long n = 2021041820210418L;
void run(