昨天以及开了一个很好地头,同时系统的梳理了一下前面的知识点,然后今天的主攻方向主要包含一下几个方面。
(1)昨天复习到的位置,即二分
(2)图论-克鲁斯卡尔,dijkstra,floyd
(3)搜索-BFS,DFS
(4)重排序-java的lamda表示形式
先给这么多吧,贪多也不好。
写到克鲁斯卡尔的时候突然想到,今天还得学一个东西,重排序
同时呢今天的内容也会加上这两天刷的题目中包含的知识点,也是我一直以来的想法。
新的一天,加油。2023.3.6 早上八点二十六分。
我的想法,从图论开始复习,因为今天的刷题打卡中就包含最短路的问题hhhh
属于是现学现用。
并查集(带权与不带权)
先放一个带权的并查集板子,不带权的其实就很简单,可以看下一个克鲁斯卡尔的前部分。
static class UF { //这里好多人报错就是因为类没有写成静态的
int[] f, d, siz;
public UF(int n) {
f = new int[n]; //父亲数组
d = new int[n];
siz = new int[n]; //当前队列长度
for (int i = 1; i < n; ++i) { //赋予初值,每个人都是自己的父亲
f[i] = i;
}
Arrays.fill(siz, 1); //一开始每个人都是一个人一队
}
int find(int x) {
if (x == f[x]) return f[x];
int root = find(f[x]);
d[x] += d[f[x]]; //相比不带权的并查集,这里只多了一句距离的处理
return f[x] = root;
}
boolean same(int x, int y) {
return find(x) == find(y); //判断两者父亲是否相同
}
boolean merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return false; //若两者具有相同的父亲,则返回false
d[y] += siz[x]; //这里因为让y排到x的后面,所以y的距离要加上x的长度
siz[x] += siz[y]; //x的长度也要加上y
f[y] = x; //y认x做呆地
return true; //认亲成功,返回TRUE
}
int size(int x) {
return siz[find(x)]; //这边单独作为一个函数,妙啊
}
int dist(int x, int y) {
if (!same(x, y)) return -1; //如果不在一个队列之中就返回-1
return Math.abs(d[x] - d[y]) - 1; //如果在一个队列之中就返回距离,记得减一
}
}
克鲁斯卡尔算法
其实本质上就是一个排序
(这边的输入数据就是从a到b的权重为w)为了简洁明了,删去一些输入部分
// 不带权并查集
public static int[] f; //父亲数组
static f = new int[n];
static for(int i=0;i<=f.length;i++)f[i]=i; //这边要改成全局,懒
public static int find(int x) {
if(f[x]==x)return x;
else {
f[x]=find(f[x]);
return f[x]; }
}
public static boolean merge(int a,int b) {
int f1 = find(a);
int f2 = find(b);
if(f1!=f2) {
f[f1]=f2;
return true; }
else return false;
}
public static void main(String[] args) {
ArrayList<edge> list = new ArrayList<edge>();
for(int i=0;i<=m;i++){
list.add(new edge(sc.nextInt(),sc.nextInt(),sc.nextInt()));
}
Collections.sort(list);
///算法真正开始的地方
for(int i =0;i<list.size();i++) {
edge check=list.get(i);
if(merge(check.start,check.end)) { //说白了,从权重最小的两条边开始合并
sum = sum+check.weight;
num++;
if(num==n-1)break; //类似DFS的输出条件,当达到n-1层时输出
}
}
System.out.println(sum); //输出权重之和
}
}
class edge implements Comparable<edge>{ //这里封装了一个类,其实解决了很多的问题
int start; //我刚开始处理输入时,对于这种就是一脸懵,总不能开三个数组吧
int end; //那开二维数组的话又不好处理数据,直接扼杀在摇篮之中
int weight; //这边提供了一个实例化的方法,或者可以用Hashmap
public edge(int s,int e, int w) {
start = s; end = e; weight = w; //java基础不好,就这么背吧,也不懂
}
public int compareTo(edge e) {
return this.weight-e.weight; //这边的话其实应该是想要按照权值排序
}
//上边如果是我自己写的话可能会是这样
public int compare(edge o1,edge o2) {
if(o1.weight > o2.weight) return 1;
return -1;
}
public String toString() { //这边涉及到继承,重写输出,直接放弃
return String.format("start=%d\t end = %d\t weight = %d\t", start,end,weight);
}
}
补充HashMap存这种数据的方式(像我这种菜鸡连数据都不会读入hhhh)
static Map<Integer,List<Integer>> map=new HashMap();
add(a[i-1], i);
static void add(int a,int b) {
if(!map.containsKey(a))map.put(a, new ArrayList<>());
map.get(a).add(b);
}
dijkstra & floyd(其中dijkstra是优化版本)
由于这俩算法除了核心部分相似度极高,所以就放在一个代码块里吧
然后其实我一开始搞不清楚这俩的区别。
区别的话就是dijkstra只能求从起点到终点的最短路
然后floyd可以求多点,好像还可以修改?记不清了
//先写到这里吧,上午还有毛概,要去吃饭啦 上午九点零三分
static int N = 300010;
static int n, m; //共有n个定点。m条边
static long[] dist = new long[N]; //从初始点到各个点之间的距离
static boolean[] st = new boolean[N]; //松弛点标记数组
//领接表
static Map<Integer, List<Node>> adj = new HashMap<>(); //我上面说的没错吧,用上了
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
while (m-- > 0) {
int a = sc.nextInt();
int b = sc.nextInt();
int w = sc.nextInt();
add(a, b, w); // 获取数据
}
dijkstra();
for (int i = 1; i <= n; i++) {
if (dist[i] >= (long) 1e18) System.out.print(-1 + " ");
else System.out.print(dist[i] + " "); //结果输出
}
}
static void dijkstra() {
Arrays.fill(dist, (long) 1e18); //将所有路径都赋予初值
dist[1] = 0; //自己到自己的距离为零
//优先队列 PriorityQueue<Node> heap = new PriorityQueue<>(Comparator.comparingLong(a -> a.d));//小顶堆
heap.offer(new Node(1, 0)); //入队,第一个节点1,(1,0)
while (!heap.isEmpty()) {
//每次放出距离最小的点
Node t = heap.poll(); //弹出第一个元素
int ver = t.idx; //idx代表点距离
long distance = t.d; //代表距离
if (st[ver]) continue;
st[ver] = true;
//更新和最小节点相连的点
List<Node> list = adj.get(ver); //获取链表中该值
if (list == null) continue; //空链表,跳出循环
for (Node node : list) { 遍历链表
int idx = node.idx;
if (dist[idx] > distance + node.d) { //进行松弛,通过松弛点缩小距离
dist[idx] = distance + node.d;
heap.offer(new Node(idx, dist[idx]));
}
}
}
}
static void add(int a, int b, int w) {
if (!adj.containsKey(a)) adj.put(a, new ArrayList<>());
//代表 a指向b有一条w的边
adj.get(a).add(new Node(b, w));
}
static void floy() {
for(int k = 0;k<vn;k++) {
for(int i=0;i<vn;i++) {
for(int j=0;j<vn;j++) {
if(g[i][k]<999 && g[k][j]<999] &&i!=j &&j!=k) g[i][j] = Math.min(g[i][j], g[i][k]+g[k][j]);
}
}
}
}
static class Node {
//点
int idx;
//代表距离
long d;
public Node(int idx, long d) {
this.idx = idx;
this.d = d;
}
}
先放个BFS的板子吧,不然上课的时候没东西研究
package month_training;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
public class BFS升级版 {
static int N = 310;
static char[][] arr = new char[N][N];
static boolean[][] visit = new boolean[N][N];
static int[] dx = new int[] {0,0,-1,1};
static int[] dy = new int[] {1,-1,0,0};
static int tmp = 2;
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int k = sc.nextInt();
for(int i=0;i<n;i++) {
arr[i] = sc.next().toCharArray();
}
Queue<int[]> queue = new LinkedList<>();
queue.offer(new int[] {2,2});
visit[2][2] = true;
int count = 0;
while(!queue.isEmpty()) {
int size = queue.size();
while(size-->0) {
int[] curr =queue.poll();
int x = curr[0];
int y = curr[1];
if(x==n-3&&y==n-3) {
System.out.println(count);
return;
}
for(int i=0;i<4;i++) {
int a = x+dx[i];
int b = y+dy[i];
if(a-tmp>=0&&a+tmp<n&&b-tmp>=0&&b+tmp<n&&!visit[a][b]&&check(a,b)) {
queue.offer(new int[] {a,b});
visit[a][b] = true;
}
}
queue.offer(new int[] {x,y});
}
count ++;
if(count == k) tmp = 1;
if(count == 2*k) tmp = 0;
}
}
static boolean check(int a,int b) {
for(int i=a-tmp;i<=a+tmp;i++) {
for(int j = b-tmp;j<=b+tmp;j++) {
if(arr[i][j] == '*') return false;
}
}
return true;
}
}
一直到晚上八点二十五分才开始继续更,今天真的学习状态好差,但是怎么说呢,有的时候忍忍就过去了,人总是要为未来的自己做一些付出的,加油!!
既然报名了省赛,就不要轻言放弃,四月份考试,时间绰绰有余,放手去干。
咱去了就是奔着省一,不然都对不起自己交的三百块钱。
然后的话下午主要做了今天的打卡题目,然后简单复习了一下二分的知识。
突然间发现我完全低估了二分的难度,代码确实很简单,但是想要发现考核的知识点是二分是一件很难的事情。所以,现在对五道例题进行逐一分析,彻底吃透。
二分的题目有一个特点,一般都是求最大最小值,但是呢又不好直接去求,暴力显然是不可取的,所以这时候就要考虑答案是否具有二段性,考虑二分。
卡牌
这里其实是二分能凑出来的卡牌套数,因为如果我二分的结果大于实际结果,那一定有一组或几组牌不符合要求,然后就可以写check函数
package test6;
import java.io.*;
public class 卡牌 {
static int N = 200010;
static int[] a = new int[N], b = new int[N];
static long n, m;
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
String[] s = br.readLine().split(" ");
n = Long.parseLong(s[0]);
m = Long.parseLong(s[1]);
s = br.readLine().split(" ");
for (int i = 1; i <= n; ++i) {
a[i] = Integer.parseInt(s[i - 1]);
}
s = br.readLine().split(" ");
for (int i = 1; i <= n; ++i) {
b[i] = Integer.parseInt(s[i - 1]);
}
//l不能开大,r不能开小,这里的2n是根据题目数据范围确定
int l = 0, r = N * 2;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
System.out.println(r);
}
//去判断能不能凑出x套牌
static boolean check(int x) {
long v = m;
for (int i = 1; i <= n; ++i) {
if (a[i] >= x) continue;
if (a[i] + b[i] < x) return false;
if (a[i] + b[i] >= x && v >= x - a[i]) {
v -= (x - a[i]);
} else {
return false;
}
}
return true;
}
}
123
前面对多个等差数列求和很精妙啊,反正我是写不出来
import java.util.Scanner;
//123
public class test {
static long[] s = new long[1500010];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long t = 0;
//预处理得到s[i]表示前i个区间的和
for (int i = 1; i <= 1500000; i++) {
t += i;
s[i] = s[i - 1] + t;
}
int n = sc.nextInt();
while (n-- > 0) {
long a = sc.nextLong(), b = sc.nextLong();
System.out.println(slove(b) - slove(a - 1));
}
}
//求前x个元素的前缀和
private static long slove(long x) {
int l = 1, r = 1500000;
while (l < r) {// 二分法快速求得 x 位于 sum 中的哪一块
int mid = l + r >> 1;
if (f(mid) < x)
l = mid + 1;
else
r = mid;
}
r--;// 求得前面块
x -= f(r);// 当前块的位置
return s[r] + f(x);// 用前面块是和+当前块的位置前缀和
}
//求出[1,n]的和
private static long f(long x) {// 等差数列求和
return (1L + x) * x / 2;
}
}
分巧克力(这里打脸了,用二维数组也可以存数据,好好学习一下)
这里其实就是二分可能的边长,因为边长的存在与否具有二分性
package test6;
import java.util.Scanner;
public class 分巧克力 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int K = sc.nextInt();
int[][] arr = new int[N][2];
for (int i = 0; i < N; ++i) {
//代表第 i 块巧克力的长
arr[i][0] = sc.nextInt();
//代表第 i 块巧克力的宽
arr[i][1] = sc.nextInt();
}
//
int l = 1;
int r = 100000;
//这就是二分的写法,如果落在符合的区间就不变,如果不符合就去掉
while (l < r) {
int mid = (l + r + 1) / 2;
if (check(arr, mid, K)) l = mid;
else r = mid - 1;
}
System.out.println(l);
}
//判断以边长X来分,是否可以分够X块
static boolean check(int[][] arr, int X, int K) {
int count = 0;
for (int i = 0; i < arr.length; ++i) {
//计算当前这块巧克力能切分多少块x*x的巧克力
int ans = (arr[i][0] / X) * (arr[i][1] / X);
count += ans;
}
return count >= K;
}
}
递增三元组
package test6;
import java.io.*;
import java.util.Arrays;
public class 递增三元组1 {
static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
static PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
static int N = 100010;
static int[] a = new int[N], b = new int[N], c = new int[N];
public static void main(String[] args) throws IOException {
int n = Integer.parseInt(br.readLine()); //这个写法真偷懒
String[] s = br.readLine().split(" ");
for (int i = 0; i < n; i++) {
a[i] = Integer.parseInt(s[i]);
}
Arrays.sort(a, 0, n);
s = br.readLine().split(" ");
for (int i = 0; i < n; i++) {
b[i] = Integer.parseInt(s[i]);
}
s = br.readLine().split(" ");
for (int i = 0; i < n; i++) {
c[i] = Integer.parseInt(s[i]);
}
Arrays.sort(c, 0, n);
long ans = 0;
//核心代码 复杂度是多少? 是不是二分了两次 nlogn^2
for (int i = 0; i < n; i++) {
//在a中二分
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (a[mid] < b[i]) l = mid;
else r = mid - 1;
}
//在c中二分
int t = r;
l = 0;
r = n - 1;
while (l < r) {
int mid = l + r >> 1;
if (c[mid] > b[i]) r = mid;
else l = mid + 1;
}
//最后判断是否符合要求
if (a[t] < b[i] && c[r] > b[i]) ans += (long) (t + 1) * (n - r);
}
out.println(ans);
out.flush();
}
}
美丽区间
package test6;
import java.io.*;
public class 美丽区间 {
static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
static PrintWriter out=new PrintWriter(new OutputStreamWriter(System.out));
static int N=100010;
static int[] a=new int[N],g=new int[N];
public static void main(String[] args) throws IOException {
String[] s=br.readLine().split(" ");
int n=Integer.parseInt(s[0]);
int t=Integer.parseInt(s[1]);
int ans=N;
s=br.readLine().split(" ");
for (int i = 1; i <=n; i++) {
a[i]=Integer.parseInt(s[i-1]);
//前缀和函数 因为我们要查询区间和
g[i]=g[i-1]+a[i];
}
//枚举 l 的复杂度是O(n)
for (int i =1; i <=n; i++) {
//二分的复杂度是 logn
int l=i,r=n;
while (l<r){
int mid=l+r>>1;
if (g[mid]-g[i-1]>=t) r=mid;
else l=mid+1;
}
//二分出来的答案不一定要求
if (g[r]-g[i-1]>=t) ans=Math.min(ans,r-i+1);
}
out.println(ans==N?0:ans);
out.flush();
}
}
🙋在这里解释一下为什么要大篇幅的列举完整代码,个人觉得二分这个方法在编程上面的难度要小于其理解上的难度,所以希望把这五个例题彻底吃透,理解,体会二分的适用场景。而不是一味的贪多,至少对我来说是这样吧。很长时间以来我对二分的映像都是很简单的题目,今天算是彻底颠覆了。算了,也谈不上,对于我这种临时抱佛脚的人来说,每天都有无数个彻底颠覆hhhh。
已经进入到test7了,争取这个星期过完全部知识点,今天是第二天,加油加油
很喜欢懂王的一句话,never!never! never give up!
重复子串问题(感觉这里的思路很棒,总共有k个组,那我就用两个for循环,每次都遍历所有组的其中一个位置的元素)
public class 重复字符串 {
static int[] cnt = new int[26];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int k = sc.nextInt();
String s = sc.next();
int n = s.length();
if (n % k != 0) {
System.out.println(-1);
return;
}
int t = n / k;
long res = 0;
for (int i = 0; i < k; i++) {
Arrays.fill(cnt, 0);
int ans = 0;
for (int j = i; j < n; j += k) {
int idx = s.charAt(j) - 'a';
cnt[idx]++;
ans = Math.max(ans, cnt[idx]);
}
res += t - ans;
}
System.out.println(res);
}
}
不同子串(这里应用了hashset中不允许存在重复的元素,很巧妙的解决了问题)
public class 不同子串 {
public static void main(String[] args) {
String s = "0100110001010001";
Set<String> set = new HashSet<>();
for (int i = 1; i <= s.length(); i++) {
for (int j = 0; j + i <= s.length(); j++) {
set.add(s.substring(j, j + i));
}
}
System.out.println(set.size());
}
}
这个hash表示给我狠狠的码住,真的很喜欢
可能和前面重复了,但是我真的很爱 这道题是数位排序,这两天有做到过
static Map<Integer, List<Integer>> map = new HashMap<>();
static void add(int a, int b) {
if (!map.containsKey(a)) map.put(a, new ArrayList<>());
map.get(a).add(b);
}
List<Integer> list = new ArrayList<>(map.keySet());
Collections.sort(list);
for (Integer integer : list) {
List<Integer> q = map.get(integer);
if (m > q.size()) {
m -= q.size();
continue;
}
Collections.sort(q);
System.out.println(q.get(m - 1));
break;
}
哇哇哇哇,今天刚刚还在想,普通数组怎么重新排序,这不就来了,hhhh
public class 数位排序2 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int m = sc.nextInt();
//如果你要自定义排序的话 Integer[]
Integer[] a = new Integer[n];
for (int i = 0; i < n; i++) {
a[i] = i+1; }
Arrays.sort(a, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
//a是o1的数位和,b是o2的数位和
int a = sum(o1);
int b = sum(o2);
if (a != b) return a - b;
else return o1 - o2; }
});
System.out.println(a[m - 1]);
}
//算数位和的方法
static int sum(Integer x) {
int res = 0;
while (x != 0) {
res += x % 10;
x /= 10;
}
return res; }}
子串分值和
这里考察的是贡献问题(考虑所有子串的情况下,绝大概率就是贡献思想)即考察上一个自己出现的位置,然后就可以算出自己可以做出的贡献,很考验思维,没见过绝对做不出来。
用字符串数组来映射下标
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Scanner;
public class 子串分值和 {
static int[] last = new int[26];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
char[] s = sc.next().toCharArray(); //用字符串数组来映射下标,很棒啊
long ans = 0;
int n = s.length;
Arrays.fill(last, -1);
//O(n)
for (int i = 0; i < n; i++) {
//加上当前位置这个字符的贡献
ans += (long) (i - last[s[i] - 'a']) * (n - i); //用距离上一个自己的距离乘以剩余的距离
last[s[i] - 'a'] = i; //这里有关距离的问题需要考虑清楚
}
System.out.println(ans);
}
}
进入test9啦
单调栈,寻找每个数上一个或下一个比她大或者比她小的数的下标
先这样吧,想刷明天的题目了
刷完回来了,什么题目,根本不是给人做的。
继续回来做笔记吧,发现test9和test10涉及的比较多的是基础的数据结构,包括栈,优先队列,队列,并查集这些。
这个留到明天晚上回来做吧。感觉除了并查集和栈和优先队列用的比较多,其他都还好....不是说的废话吗....
然后test11之中主要涉及快速幂,前缀和,对了,这两天记得补上差分的知识点。
再往后的话就是test12,其中包含BFS和DFS两种解法,其中BFS自带最短路,基本的模板上面也已经给出,且在这两个算法上的理解还算可以,所以预测复习起来会相对简单一点。也一并留到明天晚上去复习吧。
test13主要是贪心问题,可以当做不错的题目进行刷题。
test14 && test15 重点落在了动态规划上面,主要是背包问题,字符串的问题。
tips test15还涉及到了二叉树的问题;
test16主要涉及的是图论问题,包括今天已经给出的克鲁斯卡尔和dijkstra和floyd算法,纠错一下,后面两个算法主要是单源最短路和多源最短路的区别
再往后的话就是涉及到难点攻克方面。需要认真做笔记。
虽然今天的状态很差,但是呢,梳理出了这个框架,有信心在这个周完成基础知识的掌握与复习。
那么今天的代码学习就到此为止吧,不得不说,差一点点就放弃了,坚持就是胜利,加油!!!