文章目录
-
-
- [知识点] 两个数的最小公倍数&最大公因数
- [判断一个数是否为素数]
- [2017真题]k倍区间
- [2020填空]路径
- [2017填空] :纸牌三角形
- [2020 七段码]
- [2018]字母阵列
- [2015填空]三羊献瑞
- 逗志鹏危机
- 幸运的店家
- 礼物
-
算法中常见的考点和刷题记录
[知识点] 两个数的最小公倍数&最大公因数
约分法: a*b
= (这两个数字的最大公因数)* (这两个数的最小公倍数)
假设a=18,b=要求最小公倍数。
最大公因: 6 最小公倍数:90 a*b=540 可见18乘30也是如此 540;
所以 最小公倍数 = a*b / (a最大的最大公共因素) = 90 java 实现:
// 最小公倍数=乘积两个数/最大公共因数两个数。 static int gbs(int a,int b) {
return a*b/gcd(a,b) ; } // 最大公因数 // 这里最大的公因数算法是辗转相除法: a和b(a>b)最大公因数也等于a除以b获得的余数c和b最大公因数。 static int gcd(int a,int b ){
return a%b==0?b:gcd(a,a%b) ; }
参考1:最小公倍数证明 参考2:最大公因数计算&证明
[判断一个数是否为素数]
素数/质数 定义为:指在大于1的自然数中,除1和该数本身外,其他自然数不能排除的数。(也可以定义为只有1和该数本身的两个正因数)
- 0和1既不是质数也不是合数,最小质数是2
容易写的代码是:
public static boolean isPrime(int n) {
if (n <= 3) {
return n > 1; } int sqrt = (int)Math.sqrt(n); for (int i = 2; i <= sqrt; i++) {
if(n % i == 0) {
return false;
}
}
return true;
}
质数还有一个特点,它总是等于6x+1或者6x-1,其中x>=1 证明:
首先 6x 肯定不是质数,因为它能被 6 整除;其次 6x+2 肯定也不是质数,因为它还能被2整除;依次类推,6x+3 肯定能被 3 整除;6x+4 肯定能被 2 整除。那么,就只有 6x+1 和 6x+5 (即等同于6x-1) 可能是质数了。所以循环的步长可以设为 6,然后每次只判断 6 两侧的数即可。
public static boolean isPrime(int num) {
if (num <= 3) {
return num > 1;
}
// 不在6的倍数两侧的一定不是质数
if (num % 6 != 1 && num % 6 != 5) {
return false;
}
int sqrt = (int) Math.sqrt(num);
for (int i = 5; i <= sqrt; i += 6) {
if (num % i == 0 || num % (i + 2) == 0) {
return false;
}
}
return true;
}
[2017真题]k倍区间
给定一个长度为N的数列,A1, A2, … AN,如果其中一段连续的子序列Ai, Ai+1, … Aj(i <= j)之和是K的倍数,我们就称这个区间[i, j]是K倍区间。 你能求出数列中总共有多少个K倍区间吗? 输入 第一行包含两个整数N和K。(1 <= N, K <= 100000) 以下N行每行包含一个整数Ai。(1 <= Ai <= 100000) 输出 输出一个整数,代表K倍区间的数目。
例如, 输入: 5 2 1 2 3 4 5 程序应该输出: 6 资源约定: 峰值内存消耗(含虚拟机) < 256M CPU消耗 < 2000ms
首先是想到暴力法,对每一个下标都往前计算包含它的区间,符合题意就ans++ 这样做的复杂度大概是 O(n^2) 对于本题的数据量就是10^6, 直接超时。
下一步优化策略是前缀和:
下标: 0 1 2 3 4 5
原数组Q:1 2 3 4 5
前缀和S:0 1 3 6 10 15
对应关系: S[i] - S[j] = Arr[j] 但这样做的复杂度依然会很高,(只是将往前遍历的操作优化了一些)依然会超时。
最终思路:需要数学的知识(蓝桥杯的题很多需要数论知识。。)
一个数mod k 的结果肯定是 [0-k-1] ,我们试着前缀和都模k。
下标: 0 1 2 3 4 5
原数组Q: 1 2 3 4 5
前缀和S: 0 1 3 6 10 15
(S%k)S':0 1 1 0 0 1
这时候需要一个数论的定理了,同余定理:如果两个数a,b的差(a-b)能被k整除,那么就说a,b对模数k同余。
比如说 16和7, (16-7)/3 = 3 …0 , 可以得到 16%3=7%3=1 这样的结论。 我们把这个结论反过来用, 前缀和数组模k的若干个 0-k-1的数,相同余数的任意选两个相减实际上就是原数组的一个区间,同时可以被k整除,我们统计这样的组合,答案就出来了。
比如说 S2=S1=S5=1, 用S5-S2==>[3,4,5],S2-S1=[2], 从中任意挑两个相减,也就是Cm2(组合)从m个数中任意挑选2个相减 。
实现:使用一个yushu
数组,用来统计相同余数的前缀和的个数, 对于例子就是:
下标 0 1 2 3 4 5
(S%k)S' 0 1 1 0 0 1
yushu 3 3
题目的答案就是 C32+C32=3+3=6;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
class Main {
public static void main(String[] args) throws IOException {
int n=Reader.nextInt() ,k=Reader.nextInt() ;
int nums[]=new int[n+1],yushu[]=new int [k+1];
long pre[]=new long[n+1] ;
long ans =0,last=0 ;
yushu[0] = 1 ;//注意这里要手动设置为1,因为下面计算前缀和数组时,是从下标1开始计算的。
for(int i=1;i<=n;i++){
nums[i] = Reader.nextInt() ;
pre[i] = pre[i-1]+nums[i];
yushu[(int) (pre[i]%k)]++ ; // 求余数。
}
for(int i=0;i<k;i++){
ans += (long) (yushu[i] - 1) *yushu[i]/2;
// 所有前缀和余k相同的数,从中任意选择2个相减就可以得到一个k倍区间,就是组合问题 如C32
// Cn2= n(n-1)/2;
}
System.out.println(ans);
}
}
[2020填空]路径
最短路问题:我只会dijistra和floyd。 可以用dp做,更优雅。
class Main {
// 最小公倍数=两个数的乘积/两个数的最大公因数。
static long gbs(int a,int b) {
return (long) a *b/gcd(b,a) ;
}
// 最大公因数
static long gcd(int a,int b ){
return a%b==0?b:gcd(a,a%b) ;
}
public static void main(String[] args) {
int n=2021;
long[][] node = new long[n+1][n+1];
for(int i=1;i<n+1;i++) Arrays.fill(node[i],Integer.MAX_VALUE) ;
for(int i=1;i<=n;i++){
for (int j = i; j<=i+21; j++) {
if(j>2021) break ;
if(i==j) node[i][j] = node[j][i] = 0;
else node[i][j] = node[j][i] = gbs(i,j) ;
}
}
//floyd处理
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
node[i][j] =Math.min(node[i][j],node[i][k]+node[k][j]) ;
}
}
}
System.out.println(node[1][2021]) ;
}
}
[2017填空] :纸牌三角形
A,2,3,4,5,6,7,8,9 共9张纸牌排成一个正三角形(A按1计算)。要求每个边的和相等。 下图就是一种排法 这样的排法可能会有很多。 如果考虑旋转、镜像后相同的算同一种,一共有多少种不同的排法呢? 请你计算并提交该数字。 注意:需要提交的是一个整数,不要提交任何多余内容。
这是一类全排列的题目, 本题就是将9个数字全排列,然后看哪一种组合符合要求。
C++是有全排列的函数的 next_permutation
,我们java党就自己造一个把!
- 如果只有一个数字,全排列就是他自己。
- 如果有两个数字,那就每次固定一个数字的位置,然后看下一个怎么排(回到第一点了) 写到这里,就明白是递归了,第一点就是全排列的终止条件,第二点就是每次递归要做的处理。
再仔细讲讲。懂了的可以直接跳过了。看这样一个例子 : [1,2,3] [1,3,2] [2,3,1] [2,1,3] [3,1,2] [3,2,1] 可以看到数组里的每一个数字都有机会做开头数字,我们递归的过程就是不去思考后面怎么排,函数要处理的就是让将[1,2,3]三个数字逐一拉到开头,再递归下去做全排列(考虑后面的数字)
//arr:待排列的数组。参数p:数组中要实现全排列的下标开头。 q:要实现全排列的结尾下标
static void perm(int []arr,int p,int q) {
if(p==q) {
for(int i:arr) System.out.println(i);//打印排列好的数组。
return ;
}
for(int i=p;i< q;i++){
swap(arr,p,i); //交换函数:第i个数和第p个数交换
perm(arr,p+1,q) ;
swap(arr,p,i); // 换回来。
}
}
回到题目:全排列做好了,只需要检查一下结果是否符合题意。
class Main {
static int ans=0 ;
public static void main(String[] args) throws IOException {
int [] arr=new int[]{
1,2,3,4,5,6,7,8,9} ;
perm(arr,0,arr.length) ;
System.out.println(ans / 6);
}
static void check3(int []arr){
//计算三角形的三条边。
int r1= arr[0]+arr[1]+arr[3]+arr[5] ;
int r2= arr[0]+arr[2]+arr[4]+arr[8] ;
int r3= arr[5]+arr[6]+arr[7]+arr[8] ;
if(r1==r2&&r2==r3) {
//是否为正三角形。
ans++;
}
}
//arr:待排列的数组。参数p:数组中要实现全排列的下标开头。
//q:要实现全排列的结尾下标
static void perm(int []arr,int p,int q) {
//递归终止条件:p==q说明要排序的只有一个数,就是它自己。
if(p==q) {
check3(arr) ;
return ;
}
for(int i=p;i< q;i++){
swap(arr,p,i);
perm(arr,p+1,q) ;
swap(arr,p,i);
}
}
}
旋转有3种,三角形的顶点可以是三个角 /3 镜像两种: 对称轴 /2
144
相似题目还有蛮多的,可以找来做做: 2014 JavaA组 : 6角填数
[2020 七段码]
题目:
小蓝要用七段码数码管来表示一种特殊的文字。
上图给出了七段码数码管的一个图示,数码管中一共有 7 段可以发光的二 极管,分别标记为 a, b, c, d, e, f, g。
小蓝要选择一部分二极管(至少要有一个)发光来表达字符。在设计字符 的表达时,要求所有发光的二极管是连成一片的。
例如:b 发光,其他二极管不发光可以用来表达一种字符。 例如:c 发光,其他二极管不发光可以用来表达一种字符。 这种方案与上一行的方案可以用来表示不同的字符,尽管看上去比较相似。
例如:a, b, c, d, e 发光,f, g 不发光可以用来表达一种字符。 例如:b, f 发光,其他二极管不发光则不能用来表达一种字符,因为发光 的二极管没有连成一片。
请问,小蓝可以用七段码数码管表达多少种不同的字符?
dfs搜索,每一段二极管选择点亮或者不点亮,到最后检查一下符不符合条件:
class s2020{ static int ans =0 ; static HashSet<String> set=new HashSet(); public static void main(String[] args) { int []arr=new int[7] ; int []vis=new int[7] ; dfs(arr,vis,0) ; System.out.println(set.size()); } static void dfs(int []arr,int []vis,int level){ if(level==7) { check(arr) ;return ; } vis[level]=1; arr[level] =1; dfs(arr,vis,level+1) ; // 点亮的 vis[level]=0; arr[level]= 0; dfs(arr,vis,level+1) ; } static void check(int []arr){ ArrayList<Integer> tmp =new ArrayList<>(); for(int i=0;i<arr.length;i++){ if(arr[i]!=0) tmp.add(i+1) ; } arr=new int[tmp.size()] ; for(int i=0;i<arr.length;i++) arr[i]=tmp.get(i) ; if(isVaild(tmp)){ System.out.println(Arrays .toString(arr)); set.add(Arrays.toString(arr)); } } static boolean isVaild(List<Integer> arr){ if(arr.size()==1) return true ; if(arr.contains(1) && !arr.contains(2) && !arr.contains(6)) return false; if(arr.contains(4) && !arr.contains(3) && !arr.contains(5)) return false; if(arr.contains(2) && !arr.contains(1) && !arr.contains(3) && !arr.contains(7)) return false; if(arr.contains(3)