蓝桥杯省赛前的复盘

无关紧要又讨厌的细节

  1. 关于图、树的题中,尤其是树,如果可以通过集合来标定某个点的所有儿子,而不需要存在二维数组里面循环去找,就一定要用前者,后者容易超时。
  2. 用i*j/gcd(i,j)求最大公倍数
  3. “最近”,可以用bfs就用bfs
  4. 看一看算法很美中关于矩阵的操作
  5. 二叉树的基本知识点看一看 链接!
  6. 汉诺塔!
  7. 别心急
  8. 读题

java基础

calendar
主要是给出了年月日后,可以求出这一天是周几,如2013年的javaB组蓝桥杯第一题世纪末的星期,或者2020第二题纪念日

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class t {
	public static void main(String[] args) throws ParseException {
		Calendar calendar = Calendar.getInstance();// 初始化
		calendar.set(2022, 2, 26);// 设置年月日 注意月份要比实际少一,他是从0开始计数的
		System.out.println(calendar.get(Calendar.DAY_OF_WEEK));// 得出这一天是周几,注意7表示周六,1表示周日
		calendar.add(Calendar.MONTH, 1);// 月份加一
		System.out.println(calendar.getTime());
		calendar.add(Calendar.DATE, 1);// 天数加一
		System.out.println(calendar.getTime());
		System.out.println("------------------");// Date设置calendar
		SimpleDateFormat a = new SimpleDateFormat();// 设置date的格式
		a.applyPattern("yyyy-MM-dd");
		Date date = a.parse("2022-3-26");// 这里正常输入就行了
		calendar.setTime(date);
		System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
		System.out.println("--------------------------");
//		2020 年 7 月 1 日是中国共产党成立 99 周年纪念日。
//		中国共产党成立于 1921 年 7 月 23 日。
//		请问从 1921 年 7 月 23 日中午 12 时到 2020 年 7 月 1 日中午 12 时一共包
//		含多少分钟?
		Calendar calendar1 = Calendar.getInstance();// 初始化
		Calendar calendar2 = Calendar.getInstance();// 初始化
		calendar1.set(1921, 6, 23, 12, 0);
		calendar2.set(2020, 6, 1, 12, 0);
		System.out.println((calendar2.getTimeInMillis() - calendar1.getTimeInMillis()) / 1000 / 60);
	}
}

BigDecimal
和他相似的还有BigInter但是之所以写这个,是因为它涉及到精度问题,应用比较多,需要记忆一下。
就像2013年第4题的黄金连分数他要求四舍五入到小数点后100位,这就需要大分数了
● 初始化问题
尽量用字符串的方式初始化,否则会有精度问题。详看链接
● 除法精度问题
上面那个链接讲的已经很棒了。我在下面强调一下常用的方法

import java.math.BigDecimal;

public class 大分数 {
	public static void main(String[] args) {
		BigDecimal a = new BigDecimal("1");
		BigDecimal b = new BigDecimal("3");
//		System.out.println(a.divide(b));
		System.out.println(a.divide(b, 3, BigDecimal.ROUND_HALF_UP));// 四舍五入方法保留三位小数
		System.out.println(a.divide(b, 2, BigDecimal.ROUND_DOWN));
		System.out.println("-------------------------------");
		BigDecimal c = new BigDecimal("2.12345");
		System.out.println(c.setScale(4, BigDecimal.ROUND_HALF_UP));
		System.out.println(c.setScale(4, BigDecimal.ROUND_HALF_DOWN));
	}
}

如果去掉上面的注释会报错,因为它的结果是个无限循环小数,没有规定舍入方式,无法给出结果,所以报错了。

Arrays.sort 和Collections.sort
常用的就是自定义类自定义排序方法

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

public class 排序 {
	static class node {
		int x, y;

		public node(int x, int y) {
			// TODO Auto-generated constructor stub
			this.x = x;
			this.y = y;
		}
	}

	static class Myconmpator implements Comparator {// 自定义排序
		public int compare(node a, node b) {
			if (a.x < b.x)
				return -1;// 这样是升序排序???????????????
			else if (a.x > b.x)
				return 1;
			else if (a.y < b.y) {
				return -1;
			} else if (a.y > b.y) {
				return 1;
			}
			return 0;
		}
	}

	public static void main(String[] args) {
		Vector a = new Vector();
		a.add(1);
		a.add(4);
		a.add(3);
		a.add(5);
		a.add(6);
		Collections.sort(a);
		for (Integer e : a) {
			System.out.print(e + " ");
		}
		System.out.println();
		System.out.println("----------------------");
		node[] b = new node[5];
		b[1] = new node(1, 2);
		b[2] = new node(1, 3);
		b[3] = new node(2, 2);
		b[4] = new node(4, 2);
		b[0] = new node(1, 4);
//		Collections.sort(b);//Collections不能对自定义类排序,他只能对集合排序
//		Arrays.sort(b);
		Myconmpator cmp = new Myconmpator();
		Arrays.sort(b, cmp);
		for (int i = 0; i < b.length; i++) {
			System.out.println(b[i].x + ":" + b[i].y + " ");
		}
		System.out.println("-------------------------------");
		Map c = new HashMap();
		c.put(1, 4);
		c.put(1, 2);
		c.put(1, 3);
		c.put(2, 2);
		c.put(3, 2);
//		Collections.sort(c,cmp);//也不能对map进行排序
//		Arrays.sort(a, Collections.reverseOrder());//集合不能用Arrays
		Collections.sort(a, Collections.reverseOrder());// 不用自己定义逆序
		for (Integer e : a) {
			System.out.print(e + " ");
		}
		System.out.println();
		System.out.println("----------------------");
		Arrays.sort(b, Collections.reverseOrder(cmp));// 自定义类的逆序
		for (int i = 0; i < b.length; i++) {
			System.out.println(b[i].x + ":" + b[i].y + " ");
		}
	}
}
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;

public class 排序 {
	static class node implements Comparable {
		int x, y;

		public node(int x, int y) {
			// TODO Auto-generated constructor stub
			this.x = x;
			this.y = y;

		}

		public int compareTo(node b) { // 实现 Comparable 接口的抽象方法,定义排序规则
			if (this.x < b.x)
				return -1;// 这样是升序排序???????????????
			else if (this.x > b.x)
				return 1;
			else if (this.y < b.y) {
				return -1;
			} else if (this.y > b.y) {
				return 1;
			}
			return 0; // 升序排列,反之降序

		}
	}

	static class Myconmpator implements Comparator {// 自定义排序
		public int compare(node a, node b) {
			if (a.x < b.x)
				return -1;// 这样是升序排序???????????????
			else if (a.x > b.x)
				return 1;
			else if (a.y < b.y) {
				return -1;
			} else if (a.y > b.y) {
				return 1;
			}
			return 0;

		}
	}

	public static void main(String[] args) {
		System.out.println("-------TreeSet----------");
		Set d = new TreeSet();// 这里要在类里面继承comparable并重写,否则报错
		d.add(new node(1, 3));
		d.add(new node(1, 2));
		d.add(new node(2, 3));
		for (node e : d) {
			System.out.println(e.x + ":" + e.y);
		}
		System.out.println("------HashSet--------");
//		Arrays.sort(d, cmp);//报错
//		Collections.sort(d, cmp);//报错
		Set f = new HashSet();
		f.add(3);
		f.add(2);
		f.add(5);
		f.add(1);
		f.add(6);
		for (Integer e : f) {
			System.out.println(e + " ");
		} // 我没有排序但是他是有序的,说明set也是自动排序的,不需要TreeSet
		System.out.println("--------HashSet----------");
		Set g = new HashSet();// 这里要在类里面继承comparable并重写,否则报错
		g.add(new node(1, 3));
		g.add(new node(1, 2));
		g.add(new node(2, 3));
		g.add(new node(3, 2));
		for (node e : g) {
			System.out.println(e.x + ":" + e.y);
		} // 但是HashSet对node型没有排序
		System.out.println("--------TreeMap---------");
		Map h = new TreeMap();
		h.put(2, 3);
		h.put(2, 1);
		h.put(2, 4);
		h.put(5, 5);
		h.put(3, 3);
		for (Integer e : h.keySet()) {
			System.out.print(e + " ");
		}
		System.out.println();
		for (Integer e : h.values()) {
			System.out.print(e + " ");
		}
	}

集合

常用的Map、List、Set、Vector

  1. 其中Set可以去重并且排序
  2. Map可以实现Hash查找
  3. List、Vector可以通过下标遍历

感觉List和Vector也没有什么太大区别
queue集合

  1. 添加操作add
  2. 删除并且取出操作poll

集合的遍历问题
如果用迭代器遍历集合,无论对集合做增加还是删除操作都会发生错误
解决办法1:记录要改变的量,遍历完集合后再改变
解决办法2:用iterator删除

import java.util.Iterator;
import java.util.Vector;
public class 集合 {
	public static void main(String[] args) {
		Vector a = new Vector();
		a.add(1);
		a.add(4);
		a.add(3);
		a.add(5);
		a.add(6);
		Iterator it = a.iterator();
		while (it.hasNext()) {
			Integer e = it.next();
			System.out.print(e + " ");
			if (e == 3) {
				it.remove();
			}
		}
		it = a.iterator();// 这里要再次赋值,因为上一个it已经走到最后了
		System.out.println();
		while (it.hasNext()) {
			Integer e = it.next();
			System.out.print(e + " ");
		}
	}
}

集合的赋值问题
集合是引用类型,如果用等号赋值,那么他会直接变成两个不同的变量名指向同一个地址,但这并不是我的本意,这样造成一个变量变了,另一变量也会改变。奔溃的是求生成子集的一道题,用的集合,结果一直不对,后来发现不能用赋值号,要把一个集合的值给另一个集合应该直接添加!!!!!!!!!!!!!!!!代码

import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

public class tt {
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		Set> set = new HashSet>();
		Set all = new HashSet();
		for (int i = 1; i < n + 1; i++) {
			all.add(i);
			Set temp = new HashSet();
			temp.add(i);
			set.add(temp);
		}
		int k = 1;
		while ((n--) > 0) {
			Set temp = new HashSet();
			temp.addAll(all);// 一开始写了temp=all这样他们两个实际是一个地址,temp变了all也会变
			Set> temp1 = new HashSet>();
			for (Set e : set) {
				temp.removeAll(e);
//				System.out.println(e + "---");
				for (Integer i : temp) {
//					System.out.println(i + "*");
					e.add(i);
					Set ee = new HashSet();
					ee.addAll(e);// 这里也是同理不能用等号,这里没有直接添加e是因为我在后面会改变e但是e在后面变了也会导致我添加进来的e改变
					temp1.add(ee);// 这里又申请了一个,是因为我在用这样遍历方法遍历set的过程中改变set大小的话会改变它内部的一个计数的变量,这样会导致遍历出错
					// 可以用iterator的方法修改变量
//					System.out.println(e + "[[[");
					e.remove(i);

				}
				temp.addAll(all);
//				System.out.println(temp + ";");
//				for (Integer s : temp) {
//					System.out.print(s + " -");
//				}
			}
//			for (Set e : temp1) {
//				System.out.println(e);
//			}
			set.addAll(temp1);
		}
		for (Set e : set) {
			for (Integer s : e) {
				System.out.print(s + " ");
			}
			System.out.println();
		}
	}
}

这道题可谓是在集合这一块错误百出,还有将e放入集合s中如果在后面改变了e那么集合s中的e也会改变。

处理输入
用空格分割的字符串

import java.util.Scanner;
public class 输入 {
	public static void main(String[] args) {
		// f();//提取被一个空格分隔的字符串
		f1();// 提取被不规则空格个数分割的字符串
	}
	private static void f1() {
		// TODO Auto-generated method stub
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		scanner.nextLine();
		while ((n--) > 0) {
			String string = scanner.nextLine();
			String[] s = string.split("\\s+");// 使用正则表达式将字符串分割 “\\s+”表示多个空格
			for (int i = 0; i < s.length; i++) {
				System.out.print(s[i] + " ");
			}
			System.out.println();
			System.out.println("-----------------------");
		}
	}
	private static void f() {
		// TODO Auto-generated method stub
		Scanner scanner = new Scanner(System.in);
		int n = scanner.nextInt();
		scanner.nextLine();// 吸收换行符!!!!!!!!一定不能漏了
		while ((n--) > 0) {
			String string = scanner.nextLine();
			String[] s = string.split(" ");// 只能处理两数之间有一个空格
			for (int i = 0; i < s.length; i++) {
				System.out.print(s[i] + " ");
			}
			System.out.println("-----------------------");
		}
	}
}

字符串
替换字符
replace是替换字符
我大体说一下

  1. 利用字符串的截取,即我要改变下标4处的值,我从0截到4(不包括4)然后+‘我要改的值’+截取从5向后的
  2. 利用stringbuilder的setchar方法
    indexof是查找某个第一次出现的字符并返回其位置
public class 字符串 {
	public static void main(String[] args) {
		String string = "abcdefghi";
		string = string.replace('c', 'b');
		System.out.println(string);
		string = string.replaceAll("b", "a");
		System.out.println(string);
		string = string.replaceFirst("a", "g");
		System.out.println(string);
		string = string.replace("aa", "hh");
		System.out.println(string);

		String myName1 = "domanokz";
		String newName1 = myName1.substring(0, 4) + 'x' + myName1.substring(5);
		System.out.println(newName1);
		StringBuilder myName = new StringBuilder("domanokz");
		myName.setCharAt(4, 'x');
		System.out.println(myName);
		System.out.println("--------------------------------");
		string = "abcdefghi";
		System.out.println(string.indexOf('e'));// 从头开始查找
		string = "eabcdefghi";
		System.out.println(string.indexOf('e', 1));// 下标为1处开始查找

	}
}

格式化输出

System.out.println(year + "-" + String.format("%02d", mouth) + "-" + String.format("%02d", day));

两位数,缺少的前面补0

算法

必记定律

  1. 从1到n的平方数的个数=n开平方向下取整
  2. 一个数开根号后的长度lenSqr和原来长度len的关系是
    当len为偶数,lenSqrt = len / 2 .
    当len为奇数,lenSqrt = (len / 2) + 1 .
    大数开根号
    这个就是2014年的第十题矩阵翻硬币,一个模拟题最后转化为求从1到一个数中平方数的个数。
    大数开根号思想很简单,就是试探,根据定律2,我们知道了开根号后数的长度,那么就从最高位开始试探,每一位从最小的数字1开始试探,直到找到最后一个小于原数的数,然后继续找下一位,因为我一开始把所有位都赋值为0了,所以是找最后一个小于原数的数,这样我可以在后面的位数增大该数,直到它等于原数。
    当然,还有另一种方法,折半查找,也是凑平方数
// 大数开根号,字符串查找(解析)
	private static BigInteger sqrt2(String s) {
		int len = 0;
		if (s.length() % 2 == 0) {
			len = s.length() / 2;
		} else {
			len = s.length() / 2 + 1;
		}
		char[] arr = new char[len];
		Arrays.fill(arr, '0');
		BigInteger sBigInteger = new BigInteger(s);
		for (int pos = 0; pos < len; pos++) {
			for (char i = '1'; i <= '9'; i++) {
				arr[pos] = i;
				BigInteger res = new BigInteger(String.valueOf(arr)).pow(2);
				if (res.compareTo(sBigInteger) == 1) {
					// arr[pos] = (char) (arr[pos] - 1);
					arr[pos] -= 1;
					break;
				}
			}
		}
		return new BigInteger(String.valueOf(arr));

	}

	// 大数开根号,折半查找法(自己写的)
	private static BigInteger sqrt1(BigInteger n) {
		// TODO Auto-generated method stub
		BigInteger l = BigInteger.ONE;
		BigInteger r = n;
		BigInteger mid = BigInteger.ZERO;
		while (l.compareTo(r) != 1) {
			mid = (l.add(r)).divide(BigInteger.valueOf(2));
			// System.out.println(mid);
			if (mid.multiply(mid).compareTo(n) == 0) {
				break;
			}
			if (mid.multiply(mid).compareTo(n) == 1) {
				r = mid.subtract(BigInteger.ONE);
			} else {
				l = mid.add(BigInteger.ONE);
			}
		}
		if (mid.multiply(mid) != n && l.multiply(l).compareTo(n) == 1) {
			l = l.subtract(BigInteger.ONE);//减了1之后就一定保证了l的平方比n小,这倒是值得推敲
		//!!!!!!!!!!!!!!!!!!!!上面这一步很关键,退出的时候l平方可能小于大于n等于n,我们向下取,所以大于n的话要减1
        }
		mid = l;
		return mid;
	}

全排列
不详述思想,只说注意问题
看看2015第7题牌型种数,注意全排列的退出条件
2016第5题抽签 有重复的全排列

快速幂
快速幂的思想:在不超过要求幂的基础上,尽量以自身倍数的速度变化。
思想很简单,但是重点是落实在代码实现上,代码实现上,这里就又又又利用了二进制,
简单的代码
就是每次判断一下我把这个数变为倍数后是否会超出原来的次幂,如果每超出就x=x*x;
二进制2的10次幂,
把10写成二进制就是1010,因为10=8+2,所以我们需要2的8次幂乘2的2次幂,在上一中做法中,其实我们之前求过一次2的2次方,但是没有用到,后来还要再求,那么我们可以在求倍数的过程中先找到2的2次幂乘上结果,然后再找到2的8次幂乘上结果,最后就是结果,而对应二进制,就是每次倍数的时候,看幂的二进制的最后一位是否为1,如果是1那么结果乘上当前数,然后接着倍数,同时幂的二进制要右移,始终保持幂的二进制和我当前倍数表示的是一致的。

矩阵的快速幂(和普通快速幂没有区别,只是矩阵的写起来有点小麻烦)

public class 快速幂 {
	static int res = 1;
	static long[][] cnt = { { 0, 1 }, { 1, 0 } };
	public static void main(String[] args) {
		System.out.println(fib(6));
	}

	private static long fib(int i) {
		// TODO Auto-generated method stub
		if (i == 1 || i == 2) {
			return 1;
		}
		long[][] m = { { 0, 1 }, { 1, 1 } };
		long[] res = new long[2];
		mi(m, i - 2);
		long[] f = { 1, 1 };
		res = mul(f, cnt);
		return res[1];
	}

	private static void mi(long[][] m, int i) {
		if (i == 0) {
			return;
		}
		if ((i & 1) == 1) {
			cnt = mul(cnt, m);
		}
		m = mul(m, m);
		i = i / 2;
		mi(m, i);
	}

	private static long[][] mul(long[][] f, long[][] m) {
		long[][] res = new long[f.length][m[0].length];
		for (int i = 0; i < m.length; i++) {// 遍历第一个矩阵的列
			for (int j = 0; j < res.length; j++) {// 第二个矩阵的行
				long sum = 0;
				for (int k = 0; k < m.length; k++) {// 遍历第二个矩阵的行和第一个矩阵的列
					sum += f[i][k] * m[k][j];
				}
				res[i][j] = sum;
			}
		}
		return res;
	}

	private static long[] mul(long[] f, long[][] m) {
		long[] res = new long[m[0].length];
		for (int j = 0; j < res.length; j++) {// 第二个矩阵的行
			long sum = 0;
			for (int k = 0; k < m.length; k++) {// 遍历第二个矩阵的行和第一个矩阵的列
				sum += f[k] * m[k][j];
			}
			res[j] = sum;
		}
		return res;
	}

	private static void f(int i, int j) {// 11
		if (j == 0) {
			return;
		}
		if ((j & 1) == 1) {
			res *= i;
		}
		i = i * i;
		j = j / 2;
		f(i, j);
	}
}

简单数的快速幂

public class 快速幂 {
	static int res = 1;

	public static void main(String[] args) {
		f(2, 3);
		System.out.println(res);
	}

	private static void f(int i, int j) {// 11
		if (j == 0) {
			return;
		}
		if ((j & 1) == 1) {
			res *= i;
		}
		i = i * i;
		j = j / 2;
		f(i, j);
	}
}

卢卡斯定理求组合数
通过上面这个给例子可以很好的理解这个定理:简单一句话概括就是组合数取余(余数为k)的结果,等于把上下两个数求k进制后对应的数字求组合数后再取余。
来自我的笔记dp之组合数问题

乘法逆元+快速幂求组合数
2018第十题堆的计数,这一题用到了逆元是因为我要取模,但是阶乘可能本身就很大要随时取模,但是除法不能直接像乘法一样,要用逆元。
a的逆元是a的p-2次方

阶乘的逆元可以分开求

for (int i = 1; i <= n; i++) {
			f[i] = f[i - 1] * i % mod;// 求阶乘
			inv[i] = pow(i, mod - 2) * inv[i - 1] % mod;// 这样写就是优化了一些,i的阶乘的逆元等于i的逆元*(i-1)的阶乘的逆元????
//			inv[i] = pow(f[i], mod - 2) % mod;//a的逆元是a的p-2
		}

f表示i的阶乘,inv表示i的阶乘的逆元
被注释掉的是直接求的,它上面那个是分开求的,分开求的原理见下图,

康托展开和逆康托展开
全排列中结果按照字典序排序第几位

树状数组,线段树,前缀和
树状数组是利用二进制,最关键的函数是return x&-x;
线段树是二分,要写一个树的类,然后要建树,不能少。
带权并查集
并查集加上了权值,这个权值是节点到父节点的权值大小。要注意权值的定义
核心不变的代码

private static int find(int b) {
		// TODO Auto-generated method stub
		if (b != fa[b]) {
			int f = fa[b];
			fa[b] = find(fa[b]);
			dis[b] += dis[f];
		}
		return fa[b];
	}

最短路径

  1. Dijkstra算法
    初始化最开始的路径,然后每次选取当前路径中最短的一个即优中选优,用这个路径向下扩充路径。
    一共需要两层循环,第一层for循环表示我总共需要选出来n-1条路径,第二层for循环有两个,第一个选取当前路径中的最短路径,第二个用当前选出来的路径向下扩充路径

  2. Floyed算法
    核心代码:

  3. SPFA算法
    spfa运用了优先队列而非贪心

以上三种算法在if中要加上map[u][i] != Integer.MAX_VALUE不然会错,但是我不知道为什么?嗷嗷我知道了,是因为在初始化时用的是Integer.MAX_VALUE,而Integer.MAX_VALUE已经是最大了,再多加一点就变成负的了,反而成为最小的了,所以要判断一下再确定要不要加在一起。

最小生成树

  1. Prim算法
    每次找最短的一条路径,然后用这个点,更新与他相连的点
  2. Kruskal算法
    划出两个集合,先根据边的大小进行排序,选所有路径的最短边,如果这个边的两个端点都没有在最小生成树的集合内,那么我就把他加入集合,否则就不加,接着找另一条边,

dp模型

  1. 数字三角形
    从下向上遍历,为什么?一开始想从上向下遍历
    最长上升子序列
    一开始全都初始化为1
    dp[i]表示以a[i]为结尾的最长上升子序列
for{
if(a[i]>a[j]){
dp[i]=max(dp[i],dp[j]+1);
}
}
  1. 最长公共子序列
dp[i][j]=max(dp[i-1][j],dp[i][j-1])
if(s1[i]==s2[j]){
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1)
}
  1. 最大公共子串
dp[i][j]=0
if(s1[i]==s2[j]){
dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1)
}
  1. 拦截导弹
    求最长下降子序列+如果当前导弹没有被拦截,设置一个新的拦截系统,高度为该导弹的高度,然后向后找所有可以被他拦截的导弹,标记为已拦截。
    合唱队行
    最长上升子序列+最长下降子序列

贪心模型

  1. 分糖果
    简单
  2. 删除k个数字值最小
    小心考虑,按他的思路来吧,栈顶元素大于入栈元素,出栈,
  3. 跳跃问题I
    他只是判断可不可以到达,所以不用纠结在哪里跳,一直更新当前可以走的最远距离,看可不可以到达终点。
  4. 跳跃问题II
    跳跃次数最少,必须得跳的时候就跳,但是虽然我选了当前最远的距离跳,并不一定不是跳到近一点的距离,因为近一点的距离还有可能会跳到更远,接着一个一个遍历就好,只是记录跳了一次。
    射击气球问题
    有点像区间问题,但是在这里不是用区间问题解决的,比较简单的思路,逐渐缩小范围,直到下一个气球不在范围内,再加一个射手。考虑时,先考虑下一个气球的开始是不是比范围的终点大,大就可以被射中,然后看它的终点是不是小于范围的终点,小于就更新。
    最有加油方法
    和跳跃问题II差不多,但是我需要实际加油,用一个堆存所有可以加的油,每次发现当前油量到不了下一个目的地时我就选择加最大的那个油,如果还到不了接着加,直到没有油了就不加了,就到不了了。

真题

2021
只做出来三道,唉

  1. 砝码称重和最少砝码
    砝码称重考的是dp,注意初始化。
    最少砝码考的是三进制在称重中的作用。
  2. 杨辉三角形
    考了杨辉三角形的规律,算是一个数学思维找规律题
  3. 双向排序
    纯数学思维问题,和之前的那个后缀表达式差不多,模拟题中的分析题
  4. 括号序列
    数学分析+动态规划+对合法括号的理解

2020

  1. 解密
    ok
    细心
  2. 纪念日
    API
    日期API或者直接暴力求就行,但是要记住闰年是366天,是多了一天,我老是忘
  3. 合并检测
    数学
    这个怎么说,暴力,虽然没告诉总人数,恰恰说明总人数和结果无关,可以假设上,然后从1枚举k,最后手动找到最小的那个,不管前提是要手推出分组为k人时,总的检测次数,并且理解均匀分布,就是假设有3个人为阳性,这三个人一定在三个不同的检测组中。
    一句话,相信自己
  4. 分配口罩
    dfs
    这个一开始看起来毫无头绪,其实简单点就是递归,只考虑一个城市得到的口罩数,对于每一堆口罩都有选和不选两种情况,那就分别递归,最后维持一个最小戴口罩差值,自己真的好笨啊。
    背包
    但是还有另一种考虑方法,我们求出口罩数的总和后除以2,每一个城市得到的口罩和这个sum/2都有一个差值,假设一个城市的口罩数是m,m是少的那个,那么两个城市口罩数的差值就是(sum/2-m)*2所以我们只需求出最大的m即可,这个m不超过sum/2,这就有点像背包问题了,它的限制是总和不超过sum/2,但是我觉得这相当于有重量没有价值呀,背包是有价值的,其实这个题可以看作重量和价值相等而已,口罩数既可以看作重量也可以看作质量。
    但是后来运行的结果感觉前者还是比后者快。
    之前写的代码一直不通过,一个问题是sum我除以2了,后来又除了一次,另一个问题就是我定义了全局变量,但是在main函数里面又对他定义了一次,这样,我在main函数里面的变量是局部的,根本不会修改全局变量。
  5. 斐波那契数列最大公约数
    大数
    注意注意斐波那契数列真的很容易爆,这里要用大数
  6. 分类计数
    字符串
    就是字符串的遍历
  7. 八次求和
    模运算优先级
    式子很简单,但是太容易错了,一定要注意一个很长的式子里取模运算的运算优先级
    这个题不难,但是答案一直不对
    原因在于long ii = i * i % mod * i * i % mod * i * i % mod * i * i % mod;
    这样幂取模是错误的,应该是 long ii = ((i * i) % mod) * ((i * i) % mod) * ((i * i) % mod) * ((i * i) % mod);
    上面那个最后还要对结果取模
    或者
    for (int j = 0; j < 3; j++) {
    i *= i;
    i = i % mod;
    }
  8. 字符串编码
    字符串
    这个有一种要dp的题,但是这个题不用,因为要求字典序最大,所以每一次先尝试考虑最大的情况,也就是一下子读取两个字符,如果无法变成最大的,再读取一个字符。
  9. 扩展:dp
    BST插入节点问题
    排序树、树的存储和遍历
    这个利用BST的性质,一种简单的做法是直接对所有节点进行排序,在排序前记录我们要插入的节点的值temp和这个节点是否有左右孩子,然后排序后,求出temp所在的位置k,插入右边的个数就是a[k+1]-a[k]-1,插入左边的个数就是a[k-1]-a[k]-1.
    关于排序,我限于BST,想要用排序树的思路排序,但其实我已经知道了所以的值,直接存在数组中排序就行,但是它的时间是nlonn,而用排序树是n,所以nlonn超时了,但是我用排序树好像有些地方不对,运行出错了,
    不过思路的确如此。
  10. 网络分析
    带权并查集
    一个节点传输信息他会传输到所有的地方,我们只记录它的父节点的值,也就是一个节点传输信息m,我们让它的父节点的值对应加m即可,最后输出某一结点i的值就是value[find[i]]+dis[i]也就是对应父节点的值加上他们之间的差值。
    2019
  11. 组队
    ok
    细心
  12. 不同子串
    字符串截取,set去重
  13. 数列求值
    斐波那契数列容易爆
    因为只求后四位,所以只保留四位数就行了
  14. 数的分解
    暴力枚举
    一开始代码错了,读题,正整数,从1开始遍历
  15. 迷宫
    dfs或bfs
    思路很简单,但是自己一直写不对
  16. 特别数的和
    暴力枚举
    和数的分解一样的,无语
  17. 外卖店优先级
    模拟+技巧
  18. 人物相关性分析
    滑动窗口+双指针
    这个双指针还是比较特别的,具体看题吧,我限制了A和B之间的距离m,然后记录了A和B每次出现的位置,然后顺序遍历A,然后对于B,我们用L指向B到A的距离第一个小于m的位置,用R指向A到B的距离第一个大于m的位置,然后数量就是两者之差,细节自己在纸上推一下。
    这里就是以A为中心向左向右搜索,比较不好想
  19. 后缀表达式
    数学+细心+耐心
    注意负号的作用以及如何分类判断
    有负号:全为正数
    全为负数
    有正有负
    无负号:直接相加,啥也干不了
  20. 灵能传输
    前缀和+数学分析+找规律

2018

  1. 第几天
    API
    日期类
    Calendar calendar = Calendar.getInstance();
    calendar.set(2000, 4, 4);
    System.out.println(calendar.get(Calendar.DAY_OF_YEAR));
  2. 方格计数
    数学
    数学问题+分开确定每一个格子,而不是一下子确定好多个格子+点到圆心的距离
  3. 复数幂
    大数
    把结果写入文件,但最后好像超出了大数的范围,一直没成功写入
    写文件
    PrintStream ps = new PrintStream(new File(“ans.txt”));// 默认在项目的路径
    System.setOut(ps);// 输出在ans.txt里
  4. 测试次数
    dp
    这个dp确实有点难,首先初始化,应该把所有的都要初始化,其次考虑方向也有点难
    f[i][j]第i层楼还有j部手机
    f[i][j] = f[i-1][j]+f
    如果摔碎了,f[i][j] -> f[i-1][j-1]
    如果没摔碎,f[i][j] -> f[i+1][j]
    上面的思路是错的。
    f[i][j]还有i个手机目前还有j层楼没有测试的测试次数
    如果摔碎了 f[i][j] -> f[i-1][j-1]+1
    如果没有摔碎 f[i][j] -> f[i][sum-j]+1
    所以f[i][j] = min(f[i][j],max(f[i-1][k-1],f[i][sum-k])+1)
    max体现最差运气,min体现最优策略,注意是三层循环,一层手机数,一层楼层总数,一层从哪一个楼层开始试
  5. 快速排序
    快排+仔细看源代码
    快排思想好搞,关键是源代码有坑
  6. 递增三元组
    暴力枚举
  7. 螺旋折线
    看图找规律
    不好找
  8. 日志统计
    尺+双指针
  9. 全球变暖
    读题+求连通块
  10. 堆的计数
    递归+乘法逆元+快速幂求组合数+dp
    以树的思维思考问题,动态规划,
    dp[i] = c(s[i]-1,s[2*i])dp[2i]d[2i+1]
    这里的i表示树的编号,其实每个节点的值对我们来说无用,dp[i]表示以i为根节点的堆的个数
    乘法逆元的求法 a的逆元是a的p-2次方,p是模
    2017
  11. 购物单
    暴力输入
  12. 纸牌三角形
    全排列
  13. 承压计算
    暴力,要细心
  14. 魔方状态
    暂时放弃这个题了,但是我会再来
  15. 取数位
    读代码,看明白题意
  16. 最大公共子串
    还是读代码,代码填空,但是我做的话会用dp
  17. 日期问题
    模拟+细节+重要的不是思路而是细节
  18. 包子凑数
    公因数为1+暴力枚举
    如果他们公因数不为1,那么只能凑出规定倍数的数量,那么说明不能凑出来的数量是无数个,否则就是可求的,暴力求解就行
  19. 分巧克力
    暴力枚举+细节
    要从最大的边长开始枚举,而不是最小的;
    求分成的巧克力个数时用长和宽分别求,而不是用面积求。
  20. k倍区间
    dp+前缀和求模
    不太好想。两个前缀和只差的结果模k等于零,说明他们各自与k模的余数相等

2016

  1. 煤球数目
    递推+读题

  2. 生日蜡烛
    暴力枚举

  3. 凑算式
    暴力枚举

  4. 分小组
    全排列

  5. 抽签
    有重复的全排列

  6. 方格填数
    全排列+二维化一维

  7. 剪邮票

  8. 四平方和
    特殊的降低暴力复杂度的方法

  9. 取球博弈
    模拟+dfs+记忆
    关键在于记忆,还有如何实现身份的互换

  10. 压缩变换
    数学分析+大数开根号

2015

  1. 三角形面积
    小学题
  2. 立方变自身
    抽线枚举
  3. 三羊献瑞
    暴力枚举
  4. 循环节长度
    模拟除法
    for
    {n *= 10;//模拟的除法
    n = n % m;}
  5. 九数组分数
    全排列
  6. 加法变乘法
    数学技巧
    先求出总和然后枚举加法变乘法的位置,然后减去原来加上的,加上现在乘法的结果
  7. 牌型种数
    有重复的全排列
  8. 饮料换购
    算是数学问题
  9. 垒筛子
    转化为矩阵、矩阵求幂和矩阵乘法。但是我还没理解
    比较难的,用矩阵代表筛子状态,状态之间的转换用矩阵乘法,最后考的就是矩阵求幂和矩阵乘法
  10. 生命之树
    树思维
    求以i为根节点的值,一开始它的值必然加上i处的值,他就等于所有子节点的值累加,所以进入一个根后,递归完所有它的子节点后才会选择这个子节点是否加入这个根,因为题目求最大值,当根节点为负时不加入。当然因为不一定是从全树的根节点开始,所以我在求每一个以i为根节点的树的值的同时,维持一个最大值

2014

  1. 武功秘籍
    数学题
  2. 切面条
    找规律+直接撕现实中的纸条+从0开始
  3. 猜字母
    集合+细心
  4. 大衍数列
    超简单
  5. 圆周率
    找规律+细节(不要忘了减一)+从底向上推
  6. 奇怪的分式
    暴力枚举
  7. 扑克序列
    全排列也可以手推
  8. 分糖果
    细节处理+模拟
    关键转圈的时候先改前面的比较好写
  9. 地宫取宝
    dfs+记忆化+记忆数组选择记忆什么
  10. 矩阵翻硬币
    分析+0-n的平方数的个数
    2013
  11. 世纪末的星期
    日期类
  12. 马虎的算式
    暴力枚举
  13. 振兴中华
    dfs+细节
  14. 黄金连分数
    找规律或者从低向上递推
  15. 有理数类
    简单的代码填空
  16. 三部排序
    快排三指针的应用
  17. 错误票据
    动态数组+排序
  18. 幸运数
    处理细节+删除数组元素
  19. 带分数
    全排列+根据题目枚举加号和乘号的位置
  20. 连号区间数
    这道题只要知道什么是连号区间号就行了,就是最大值与最小值的差值等于区间长度枚举左右区间端点

今年终于算是结束了蓝桥杯的比赛,以自己比较满意的成绩,从大一到大二,两次比赛,最终以幸运结尾。
以上只是我在临近省赛时做的总结,内容很大但不细节,如果有什么关于算法或蓝桥杯赛题的疑问,欢迎来我公众号问我,虽然我很菜,但是我们一起成长呀,加油!!!!!!!!!!
蓝桥杯省赛前的复盘_第1张图片

你可能感兴趣的:(蓝桥杯,蓝桥杯,java)