冲击省赛的第二天学习内容记录

昨天以及开了一个很好地头,同时系统的梳理了一下前面的知识点,然后今天的主攻方向主要包含一下几个方面。

(1)昨天复习到的位置,即二分

(2)图论-克鲁斯卡尔,dijkstra,floyd

(3)搜索-BFS,DFS

(4)重排序-java的lamda表示形式

先给这么多吧,贪多也不好。

写到克鲁斯卡尔的时候突然想到,今天还得学一个东西,重排序

同时呢今天的内容也会加上这两天刷的题目中包含的知识点,也是我一直以来的想法。

新的一天,加油。2023.3.6 早上八点二十六分。


我的想法,从图论开始复习,因为今天的刷题打卡中就包含最短路的问题hhhh

属于是现学现用。


  1. 并查集(带权与不带权)

先放一个带权的并查集板子,不带权的其实就很简单,可以看下一个克鲁斯卡尔的前部分。

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;   //如果在一个队列之中就返回距离,记得减一
    }
}
  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);
    }
  1. 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;
            }
        }
        
  1. 先放个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算法,纠错一下,后面两个算法主要是单源最短路和多源最短路的区别

再往后的话就是涉及到难点攻克方面。需要认真做笔记。

虽然今天的状态很差,但是呢,梳理出了这个框架,有信心在这个周完成基础知识的掌握与复习。


那么今天的代码学习就到此为止吧,不得不说,差一点点就放弃了,坚持就是胜利,加油!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值