C:直线
【问题描述】
在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一条。
给定平面上 2 × 3 2 × 32×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 00 到 1 11 (包含 0 00 和 1 11) 之间的整数、纵坐标是 0 00 到 2 22 (包含 0 00 和 2 22) 之间的整数的点。这些点一共确定了 11 1111 条不同的直线。
给定平面上 20 × 21 20 × 2120×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 00 到 19 1919 (包含 0 00 和 19 1919) 之间的整数、纵坐标是 0 00 到 20 2020 (包含 0 00 和 20 2020) 之间的整数的点。请问这些点一共确定了多少条不同的直线。
【答案】
40257
【难点】
①本题的难点个头决定在于精度的处理,非常容易处理不好
【思路】
①利用直线方程 y = kx + b,但是不是直接利用,由于考虑到精度,
k = (y2-y1) / (x2-x1);
b = (y1*x2 - x1*y2)。
(此时这里就有两个大坑——
1、分子分母需要 * 1.0否则循环出来都是整数;
2、分母/分子会出现除不尽的情况,这时候我们需要把分母和分子最简化,比如 9/12 -> 3/4,涉及最大公约数,这是最后优化精度的一个关键点)。
②就是利用HashMap,键存储斜率k,值是一个HashSet集合存储截距b。
③然后直接遍历,4个for循环解决。
【代码】
public class LongStraight {
public static void main(String[] args) {
int res = 0;
//利用 y = kx + b;
//斜率不存在的直线数 即 x的点数 20(0-19)
res = 20;
//用map存储
Map> map = new HashMap<>();
double k = 0; //斜率
double b = 0; //截距
HashSet set = new HashSet<>();
for (int x1 = 0; x1 < 20; x1++) {
for (int y1 = 0; y1 < 21; y1++) {
for (int x2 = 0; x2 < 20; x2++) {
for (int y2 = 0; y2 < 21; y2++) {
//斜率存在
if (x1 != x2) {
//这里是有个坑的,x1 x2 y1 y2都是整数,需要 *1.0变成双精度
int muk = (x2 - x1);
int zik = (y2 - y1);
int tk = gcd(muk, zik);
k = (zik/tk) * 1.0 / (muk/tk);
//b = y1 - k * x1; //精度太差,但是往往考试时很容易就这么写
int mub = (x2 - x1);
int zib = (y1*x2 - x1*y2);
int tb = gcd(mub, zib);
b = (zib/tb) * 1.0 / (mub/tb);
set = map.getOrDefault(k, new HashSet());
set.add(b);
map.put(k, set);
}
}
}
}
}
for (HashSet resSet : map.values()) {
res += resSet.size();
}
System.out.println(res); //40257 -- 就是答案
}
//最大公约数
static int gcd(int a, int b) {
return b == 0? a : gcd(b, a%b);
}
}
H:杨辉三角形
【问题描述】
图形是著名的杨辉三角形:
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:
1 , 1 , 1 , 1 , 2 , 1 , 1 , 3 , 3 , 1 , 1 , 4 , 6 , 4 , 1 , . . . 1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, ...1,1,1,1,2,1,1,3,3,1,1,4,6,4,1,...
给定一个正整数 N,请你输出数列中第一次出现 N 是在第几个数?
【思路参考】
http://H:杨辉三角形 【问题描述】 下面的图形是著名的杨辉三角形: 如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列: 1 , 1 , 1 , 1 , 2 , 1 , 1 , 3 , 3 , 1 , 1 , 4 , 6 , 4 , 1 , . . . 1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, ...1,1,1,1,2,1,1,3,3,1,1,4,6,4,1,... 给定一个正整数 N,请你输出数列中第一次出现 N 是在第几个数? ———————————————— 版权声明:本文为CSDN博主「en_oc」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/TraceOfTwilight/article/details/116072583
首先杨辉三角,我们要的数据其实只有一半,当前迭代的时候也需要另一半,上面的文章二个key点:
1、卡好到第三列(数据为前n项和),利用n*(n+1)/2 < 10亿(最大用例数)得n<44721,当我把前面的44721层都暴力搜完了,还是找不到这个数,说明这个数一定会在第二列,为什么?
因为第三列及后面到(n+1)/2列的全部数据都大于10亿了,再搜没有意义了,同时也不够空间去搜了,而第一列全部都是1,所以一定在第二列,第二列就是一个1,1,2,3,4,5,。。。的数列,利用等差数列,直接可以求出位置 n*(n+1)/2。
2、有点动态规划的feeling,就是创建的一维数组,我们只需在每层遍历时,从后往前即可,这样内存也不会太过。
//从第三层开始遍历——对应索引2
for (int i = 2; i < nums.length; i++) {
//从后往前遍历,就可以刚刚好地替代旧数据且不会使用到新数据,j=0的位置一直为1不需更新
for (int j = i; j > 0; j--) {
nums[j] += nums[j-1];
if (nums[j] == n) { //注意我们这里找到的是后往前的数据,对称的j坐标才是我们想要
System.out.println(i - j + 1 + k);
return;
}
}
k += i + 1;
}
【当然这里有个注意点】
就是一定得用Long类型的,包括接受的参数n,数组nums,否则就会出现负数等无法预料的情况。
/*
n为int类型(位数不够)
100000777
-769133009
n为long类型(正解)
100000777
5000077750302255
*/
【代码】
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
Long n = scan.nextLong();
scan.close();
if (n == 1) {
System.out.println(n);
return;
}
long[] nums = new long[44721];
nums[0] = 1;
nums[1] = 1;
Long k = 3L; //至第二层共3个数
//从第三层开始遍历——对应索引2
for (int i = 2; i < nums.length; i++) {
//从后往前遍历,就可以刚刚好地替代旧数据且不会使用到新数据,j=0的位置一直为1不需更新
for (int j = i; j > 0; j--) {
nums[j] += nums[j-1];
if (nums[j] == n) { //注意这里找到的是后往前的数据,对称的j坐标才是我们想要
System.out.println(i - j + 1 + k); //比如 1 3 3 1
return; //我们搜到的是索引2,但是其实我们需要1
} // i+1 - j
}
k += i + 1;
}
//前44721层都找不到,那么必定在44722层后的第二列,因为此时其他3-n/2+1列的数值已超过十亿
// 数n会在第n+1层的第2列 位数为:前n层的数据数的前n项和 n*(n+1)/2
System.out.println(n*(n+1)/2 + 2);
}
}
题目三:2021年蓝桥杯填空——路径
【问题描述】
小蓝学习了最短路径之后特别高兴,他定义了一个特别的图,希望找到图
中的最短路径。
小蓝的图由 2021 个结点组成,依次编号 1 至 2021。
对于两个不同的结点 a, b,如果 a 和 b 的差的绝对值大于 21,则两个结点之间没有边相连;如果 a 和 b 的差的绝对值小于等于 21,则两个点之间有一条长度为 a 和 b 的最小公倍数的无向边相连。
例如:结点 1 和结点 23 之间没有边相连;结点 3 和结点 24 之间有一条无向边,长度为 24;结点 15 和结点 25 之间有一条无向边,长度为 75。
请计算,结点 1 和结点 2021 之间的最短路径长度是多少。
【答案】
10266837
【思路】
显然的两种最短路方法Floyd,Dijsktra,推荐上使用第二种,时间快,毕竟题目是单源最短路。
public class Main {
public static void main(String[] args) {
//题目确定了首尾节点 1 2021,属于单源最短路径,可以有floyd或者dijsktra
//首先创建邻接矩阵 1、对角元为0即可,也符号实际意义 2、邻接矩阵是对称的,出于时间考虑,我们直接算上三角,遍历时赋给下三角即可
// 2021*2021都不足以用long,所以我们这里不必考虑长整型这类
int[][] near = new int[2021][2021];
int min = Integer.MAX_VALUE / 2;
for (int i = 0; i < near.length; i++) {
for (int j = i+1; j < near.length; j++) {
if (j - i > 21) {
near[i][j] = min;
} else {
near[i][j] = lcm(i+1, j+1);
}
near[j][i] = near[i][j];
}
}
//floyd(near); //10266837
dijsktra(near, 0); //10266837
System.out.println(near[0][2020]);
}
// 单源最短路,且必须为正权值,但速度快 o(n^2)
static void dijsktra(int[][] near, int start) {
int[] visited = new int[near.length]; //记忆数组
visited[start] = 1; //标记起点以开始循环
//循环n-1次,仅仅是为了循环n-1次,k无实际意义
for (int k = 1; k < near.length; k++) {
int curMin = Integer.MAX_VALUE / 2;
int t = start; //记录中间节点
//寻找最短边的尾点最为中间节点
for (int i = 0; i < near.length; i++) {
if (visited[i] == 0 && near[start][i] < curMin) {
curMin = near[start][i];
t = i;
}
}
visited[t] = 1; //标记
//更新
for (int j = 0; j < near.length; j++) {
if (visited[j] == 0) {
near[start][j] = Math.min(near[start][j], near[start][t] + near[t][j]);
}
}
}
}
//多源最短路,时间偏长,但可用于有负权图的情况
static void floyd(int[][] near) {
// o(n^3) 有点暴力解的感觉
//中间节点开始循环
for (int k = 0; k < near.length; k++) {
for (int i = 0; i < near.length; i++) {
for (int j = 0; j < near.length; j++) {
near[i][j] = Math.min(near[i][j], near[i][k] + near[k][j]);
}
}
}
}
//最小公倍数
static int lcm(int a, int b) {
return a / gcd(a, b) * b;
}
//最大公约数
static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a%b);
}
}
【总结】
①蓝桥杯的题目一定要注意好精度和位数,int类型甚至可以直接无脑改成long类型,特别是数据用例过一亿的那种。