最全蓝桥杯省赛国赛知识点总结

写在开头: 读者们,俯听我一言:本篇文章全面的讲解了各个知识点,并且为每一个知识点提供了模板,只要掌握其中的要点,您轻松就能够获得省一。如果您在阅读过程中有任何疑问或者纰漏之处,请随时私信联系我,我的耐心答复必将令您满意🤩。

一、数论

1.判断质数

注意 i i i 的终止条件本来是 i < = s q r t ( n u m ) i<=sqrt(num) i<=sqrt(num),但是计算开方太慢了,所以用 i ∗ i < = n u m i * i<=num ii<=num
但是 i ∗ i i*i ii容易爆int,所以最终改成 i < = n u m / i i <=num / i i<=num/i,后面的其他代码也会采用这种写法,希望不会搞晕。

 public static boolean is_prime(int num) {
        for (int i = 2; i <= num / i; i++) {
            if (num % i == 0) {
                return false;
            }
        }
        return true;
    }

2.最大公约数

public static int gcd(int a, int b) {
        if (b == 0) return a;
        return gcd(b, a % b);
    }

3.最小公倍数

a和b最小公倍数是 a ∗ b g c d ( a , b ) \frac{a*b}{gcd(a,b)} gcd(a,b)ab,由于a*b可能爆int,所以采用先除再乘, l c m ( a , b ) = a g c d ( a , b ) ∗ b lcm(a,b)=\frac{a}{gcd(a,b)}*b lcm(a,b)=gcd(a,b)ab

	public static int lcm(int a, int b){
        return a/gcd(a,b)*b;
    }
    public static int gcd(int a, int b) {
        while (b != 0) {
            int r = a % b;
            a = b;
            b = r;
        }
        return a;
    }

4.筛质数

下面的两种方法可以得到 小于等于n 的所有质数。

要想一次性得到很多质数,那么就要使用质数筛法,质数筛法有很多方法,现在给出一种 埃及筛法。这种方法写起来比较简单,而且比较快,强烈推荐这种写法。时间复杂度是: O ( n l o g l o g n ) O(nloglogn) O(nloglogn)

public static void getPrimes(int n) {
        boolean[] is_prime = new boolean[n + 1];
        Arrays.fill(is_prime, true);
        for (int i = 2; i <= n / i; i++) {
            if (is_prime[i]) {
                for (int j = i * i; j <= n; j += i) {
                    is_prime[j] = false;
                }
            }
        }
        for (int i = 2; i <= n ; i++) {
            if (is_prime[i]){
                System.out.println(i);
            }
        }
    }

5.欧拉函数

欧拉函数:用于求小于等于n的数与n互质的个数是几个。
欧拉函数是积性函数,也就是 f ( a ∗ b ) = f ( a ) ∗ f ( b ) f(a*b)=f(a)*f(b) f(ab)=f(a)f(b)

	public static int f(int n) {
        int res = n;
        for (int i = 2; i <= n / i; i++) {
            if (n % i == 0) {
                res -= res / i;
                while (n % i == 0) {
                    n /= i;
                }
            }
        }
        if (n > 1) {
            res -= res / n;
        }
        return res;
    }

6.区间欧拉函数

该代码用于求小于等于n的数,它们每一个欧拉函数值φ(n)。
此外,当n为质数时, φ ( n ) = n − 1 φ(n) = n-1 φ(n)=n1

	public static int[] oula(int n) {
        int[] res = new int[n + 1];
        for (int i = 0; i < res.length; i++) {
            res[i] = i;
        }
        for (int i = 2; i <= n; i++) {
            if (res[i] == i) {
                for (int j = i; j <= n; j += i) {
                    res[j] -= res[j] / i;
                }
            }
        }
        return Arrays.copyOfRange(res, 1, n + 1);
    }

7.计算所有约数个数

12的约数有:1,2,3,4,6,12
所以12的约数个数是6

	public static int count(int n) {
        public static long getSum(int n) {
        long result = 0L;
        for (int i = 1; i <= n / i; i++) {
            if (n % i == 0) {
                result++;
                if (i * i != n) {
                    result ++;
                }
            }
        }
        return result;
    }
    }

8.计算所有约数和

12的约数有:1,2,3,4,6,12
所以12的约数个数是28

	public static long getSum(int n) {
        long result = 0L;
        for (int i = 1; i <= n / i; i++) {
            if (n % i == 0) {
                result += i;
                if (i * i != n) {
                    result += n / i;
                }
            }
        }
        return result;
    }

9.计算质因子个数

12的约数有:1,2,3,4,6,12
所以12的质约数个数是2

	public static void count(int n) {
        int count = 0;
        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) {
                count++;
                while (n % i == 0) {
                    n /= i;
                }
            }
        }
        if (n > 1) {
            count++;
        }
        System.out.println(count);
    }

10.扩展欧几里得

扩展欧几里得:用途是求解 a x + b y = g c d ( a , b ) ax+by = gcd(a,b) ax+by=gcd(a,b) 中的 x 和 y x和y xy 的值
已知,gcd(a,b) = d, 则存在 x,y 使得 a x + b y = d = g c d ( a , b ) ax+by = d = gcd(a,b) ax+by=d=gcd(a,b)
特别的,如果a,b互质,当且仅当 a x + b y = 1 ax+by=1 ax+by=1
如果 v % d = 0 v\%d=0 v%d=0,那么 a x + b y = v ax+by=v ax+by=v 的解是 a x + b y = d ax+by=d ax+by=d 的解的 v d \frac{v}{d} dv 倍数

	public static int[] ex_gcd(int a, int b) {
        if (b == 0) {
            return new int[]{a, 1, 0};
        } else {
            int[] result = ex_gcd(b, a % b);
            return new int[]{result[0], result[2], result[1] - (a / b) * result[2]};
        }
    }

12.快速幂

Java中有Biginteger类,可以实现该操作。

	public static int quickPower(int a, int b) {
        if (b == 1) return a;
        int half = quickPower(a, b / 2);
        if (b % 2 == 0) {
            return half * half;
        } else {
            return half * half * a;
        }
    }

13.快速幂取模

Java可以用Biginteger类的power和mod方法,更方便。但是,这种思想也要掌握。

	public static int quickPowerMod(int a, int b, int c) {
        if (b == 1) return a;
        int half = quickPowerMod(a, b / 2, c) % c;
        if (b % 2 == 0) {
            return half * half;
        } else {
            return half * half * a;
        }
    }

14.欧拉定理

φ ( n ) φ(n) φ(n) 表示n的欧拉函数值,那么:
欧拉定理:若a和n互质,则 a φ ( n ) = 1 ( m o d   n ) a^{φ(n)}=1(mod\ n) aφ(n)=1(mod n)
费马定理:若a和n互质,并且n为质数,那么 φ(n) = n-1,此时 a n − 1 = 1 ( m o d   n ) a^{n-1}=1(mod\ n) an1=1(mod n)

根据上面的两个定理,我们可以得到 a关于n的逆元

若a和n互质,则 a − 1 ( m o d   n ) = a φ ( n ) − 1 a^{-1}(mod\ n)=a^{φ(n) -1} a1(mod n)=aφ(n)1
若a和n互质,并且n为质数,那么 φ(n) = n-1,此时 a − 1 ( m o d   n ) = a n − 2 a^{-1}(mod\ n)=a^{n-2} a1(mod n)=an2

15.求a关于b的逆元

若a和n互质,则 a − 1 ( m o d   n ) = a φ ( n ) − 1 a^{-1}(mod\ n)=a^{φ(n) -1} a1(mod n)=aφ(n)1
若a和n互质,并且n为质数,那么 a − 1 ( m o d   n ) = a n − 2 a^{-1}(mod\ n)=a^{n-2} a1(mod n)=an2

使用快速幂

a − 1 = q u i c k P o w e r M o d ( a , n − 2 , n ) a^{-1}=quickPowerMod(a,n-2,n) a1=quickPowerMod(a,n2,n)

	public static int quickPowerMod(int a, int b, int c) {
        if (b == 1) return a;
        int half = quickPowerMod(a, b / 2, c) % c;
        if (b % 2 == 0) {
            return half * half;
        } else {
            return half * half * a;
        }
    }
    public static int f(int a, int b) {
        return quickPowerMod(a, b-2, b);
    }

16.中国剩余定理

中国剩余定理(Chinese Remainder Theorem,CRT)是解决一类同余方程组的经典方法,它可以在同余方程组中减少运算的数量,从而提高计算效率。其公式为:

x ≡ a i   m o d   m i ,   i = 1 , 2 , . . . , n x \equiv a_i \bmod m_i,\ i = 1,2,...,n xaimodmi, i=1,2,...,n

其中 m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m1,m2,...,mn 两两互质, M = m 1 × m 2 × . . . × m n M = m_1 \times m_2 \times ... \times m_n M=m1×m2×...×mn M i = M / m i M_i = M/m_i Mi=M/mi。则同余方程组的解为:

x ≡ ( ∑ i = 1 n a i M i x i )   m o d   M x \equiv (\sum_{i=1}^{n} a_iM_ix_i) \bmod M x(i=1naiMixi)modM

其中 x i x_i xi M i M_i Mi 在模 m i m_i mi 意义下的逆元,即 M i x i ≡ 1   m o d   m i M_ix_i \equiv 1 \bmod m_i Mixi1modmi

注意,当 m 1 , m 2 , . . . , m n m_1,m_2,...,m_n m1,m2,...,mn 不互质时,同余方程组中不一定存在解,此时需要进行合并或者降维(取模)等操作,转化为互质的情况,然后再使用CRT进行求解。

二、背包问题

1. 01 背包(采用状态压缩)

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int M = scanner.nextInt();
        int N = scanner.nextInt();
        int[] value = new int[N + 1];
        int[] weight = new int[N + 1];
        int[] dp = new int[M + 1];
        for (int i = 1; i <= N; i++) {
            weight[i] = scanner.nextInt();
            value[i] = scanner.nextInt();
        }
        for (int i = 1; i <= N; i++) {
            for (int j = M; j >= weight[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        System.out.println(dp[M]);
    }

2. 多重背包

	public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        //容量为M
        int M = scanner.nextInt();
        //种类
        int N = scanner.nextInt();
        //价值
        int[] value = new int[N + 1];
        //占用体积
        int[] weight = new int[N + 1];
        //个数
        int[] num = new int[N + 1];
        for (int i = 1; i <= N; i++) {
            value[i] = scanner.nextInt();
            weight[i] = scanner.nextInt();
            num[i] = scanner.nextInt();
        }
        int[] dp = new int[M + 1];
        for (int i = 1; i <= N; i++) {
            for (int j = M; j >= weight[i]; j--) {
                for (int k = 0; k <= num[i] && k * weight[i] <= j; k++) {
                    dp[j] = Math.max(dp[j], dp[j - k * weight[i]] + k * value[i]);
                }
            }
        }
        System.out.println(dp[M]);
    }

3. 完全背包

	public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int M = scanner.nextInt();
        int[] value = new int[N + 1];
        int[] weight = new int[N + 1];

        for (int i = 1; i <= N; i++) {
            value[i] = scanner.nextInt();
            weight[i] = scanner.nextInt();
        }
        int[] dp = new int[M + 1];
        for (int i = 1; i <= N; i++) {
            for (int j = weight[i]; j <= M; j++) {
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }

    }

4.二维背包

	public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int N = scanner.nextInt();
        int M = scanner.nextInt();
        int V = scanner.nextInt();
        int[] weight = new int[N + 1];
        int[] volume = new int[N + 1];
        int[] value = new int[N + 1];
        for (int i = 1; i <= N; i++) {
            weight[i] = scanner.nextInt();
            volume[i] = scanner.nextInt();
            value[i] = scanner.nextInt();
        }
        int[][] dp = new int[M + 1][V + 1];
        for (int i = 1; i <= N; i++) {
            for (int j = M; j >= weight[i]; j--) {
                for (int k = V; k >= volume[i]; k--) {
                    dp[j][k] = Math.max(dp[j][k], dp[j - weight[i]][k - volume[i]] + value[i]);
                }
            }
        }
        System.out.println(dp[M][V]);
    }

5.例题1:子集和_可重复

求从一些不重复数字中选取一些数字使得和为target的方案数,每个数字可以重复使用,求方案数。这是一个完全背包问题。

public static void main(String[] args) {
        int[] nums = new int[]{0, 1, 2, 3, 4, 5, 6,7,8,9,10};
        int target = 12;
        int N = 10;
        int[] dp = new int[target + 1];
        dp[0] = 1;
        for (int i = 1; i <= N; i++) {
            for (int j = nums[i]; j <= target; j++) {
                dp[j] += dp[j - nums[i]];
            }
        }
        System.out.println(dp[target]);
    }

6.例题2:子集和_不可重复

求从一些不重复数字中选取一些数字使得和为target的方案数,每个数字最多只能选一次,求方案数。这是一个01问题。

public static void main(String[] args) {
        int[] nums = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int target = 12;
        int N = 10;
        int[] dp = new int[target + 1];
        dp[0] = 1;
        for (int i = 1; i <= N; i++) {
            for (int j = target; j >= nums[i]; j--) {
                dp[j] += dp[j - nums[i]];
            }
        }
        System.out.println(dp[target]);
    }

7.例题3:2022

从1到2022个数中,选择10个数,求这10个数之和为2022的方案数。这是一个二维背包问题。

	public static void main(String[] args) {
        long[][] dp = new long[11][2023];
        dp[0][0] = 1;
        for (int i = 1; i <= 2022; i++) {
            for (int j = 10; j >= 1; j--) {
                for (int k = 2022; k >= i; k--) {
                    dp[j][k] += dp[j - 1][k - i];
                }
            }
        }
        System.out.println(dp[10][2022]);
    }

三、图论

1.邻接表

	static class Edge {
        int next;
        int value;
        public Edge(int next, int value) {
            this.next = next;
            this.value = value;
        }
    }
    static HashMap<Integer, LinkedList<Edge>> graph = new HashMap<>();
    public static void addEgde(int from, int to, int value) {
        if (!graph.containsKey(from)) {
            graph.put(from, new LinkedList<>());
        }
        if (!graph.containsKey(to)) {
            graph.put(to, new LinkedList<>());
        }
        graph.get(from).add(new Edge(to, value));
        graph.get(to).add(new Edge(from, value));
    }

2.dijkstra

求单源最短路,不能求带有负权边

import java.util.*;
public class Main{
    static class Edge {
        int next;
        int value;
        public Edge(int next, int value) {
            this.next = next;
            this.value = value;
        }
    }
    static HashMap<Integer, LinkedList<Edge>> graph = new HashMap<>();
    //用于记录起始点到每个点的最短距离
    static int[] distance;
    //用于记录每个点是否被标记过,如果被标记过说明这个点已经是最短路径了
    static boolean[] visited;
    static int N;
    static int M;
    public static void addEgde(int from, int to, int value) {
        if (!graph.containsKey(from)) {
            graph.put(from, new LinkedList<>());
        }
        if (!graph.containsKey(to)) {
            graph.put(to, new LinkedList<>());
        }
        graph.get(from).add(new Edge(to, value));
        graph.get(to).add(new Edge(from, value));
    }
    public static void dijkstra(int start) {
        Arrays.fill(distance, Integer.MAX_VALUE);
        Arrays.fill(visited, false);
        distance[start] = 0;
        int N = graph.size();
        for (int i = 0; i < N; i++) {
            //1.找到distance的最小值
            int min = Integer.MAX_VALUE;
            int min_index = -1;
            for (int j = 0; j < distance.length; j++) {
                if (!visited[j] && min > distance[j]) {
                    min = distance[j];
                    min_index = j;
                }
            }
            if (min_index == -1) {
                return;
            }
            //2.标记min_index=true
            visited[min_index] = true;
            //3.更新从min_index出发的边
            LinkedList<Edge> edges = graph.get(min_index);
            for (int j = 0; j < edges.size(); j++) {
                int next = edges.get(j).next;
                int value = edges.get(j).value;
                if (!visited[next] && distance[min_index] + value < distance[next]) {
                    distance[next] = distance[min_index] + value;
                }
            }
        }
        //输出结果
        System.out.println(Arrays.toString(distance));
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N = scanner.nextInt();
        M = scanner.nextInt();
        int start = scanner.nextInt();
        for (int i = 0; i < 9; i++) {
            int from = scanner.nextInt();
            int to = scanner.nextInt();
            int value = scanner.nextInt();
            addEgde(from, to, value);
        }
        distance = new int[N + 1];
        visited = new boolean[N + 1];
        dijkstra(start);
    }
}

3.dijkstra优化版本

import java.util.*;
public class Main {
    static class Edge {
        int next;
        int value;
        public Edge(int next, int value) {
            this.next = next;
            this.value = value;
        }
    }
    static class Distance implements Comparable {
        int node;
        int dis;
        public Distance(int node, int dis) {
            this.node = node;
            this.dis = dis;
        }
        @Override
        public int compareTo(Object o) {
            Distance object = (Distance) o;
            return Integer.compare(this.dis, object.dis);
        }
    }
    static HashMap<Integer, LinkedList<Edge>> graph = new HashMap<>();
    static int N;
    static int M;
    static int[] distance;
    static boolean[] visited;
    static PriorityQueue<Distance> queue = new PriorityQueue<>();
    public static void addEdge(int from, int to, int value) {
        if (!graph.containsKey(from)) {
            graph.put(from, new LinkedList<>());
        }
        if (!graph.containsKey(to)) {
            graph.put(to, new LinkedList<>());
        }
        graph.get(from).add(new Edge(to, value));
        graph.get(to).add(new Edge(from, value));
    }
    public static void dijkstra(int start) {
        Arrays.fill(distance, Integer.MAX_VALUE);
        distance[start] = 0;
        //0.初始优先队列
        queue.add(new Distance(start, 0));
        while (!queue.isEmpty()) {
            //1.从优先队列中弹出优先级最高的,找到最小的distance,然后标记
            Distance dis = queue.poll();
            if (visited[dis.node]) continue;
            int min_index = dis.node;
            int min_dis = dis.dis;
            visited[min_index] = true;
            //2.更新从min发出的所有边
            LinkedList<Edge> edges = graph.get(min_index);
            for (Edge edge : edges) {
                int next = edge.next;
                int value = edge.value;
                if (!visited[next] && distance[next] > min_dis + value) {
                    distance[next] = min_dis + value;
                    //3.压入优先队列
                    //虽然优先队列中会出现一个点的多个distance,但是后面的用不到
                    queue.add(new Distance(next, distance[next]));
                }
            }
        }
        for (int i = 1; i < distance.length; i++) {
            System.out.print(distance[i] + " ");
        }
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N = scanner.nextInt();
        M = scanner.nextInt();
        int start = scanner.nextInt();
        for (int i = 0; i < M; i++) {
            int from = scanner.nextInt();
            int to = scanner.nextInt();
            int value = scanner.nextInt();
            addEdge(from, to, value);
        }
        distance = new int[N + 1];
        visited = new boolean[N + 1];
        dijkstra(start);
    }
}

4.Floyd

作用:可以用于求带负权边的多源最短路径,并且只能用于邻接矩阵。
Floyd是采用动态规划的思想实现的。
设dp[k][i][j]表示只用前 k 个点时,i 到 j 之间的最短路径是dp[k][i][j]
初始化:dp[0][i][j]表示点与点之间的距离
状态转移:dp[k][i][j] = min(dp[k-1][i][j],dp[k-1][i][k]+dp[k-1][k][j]),注意k是最外层循环
然后进行空间压缩:dp[i][j] = min(dp[i][j],dp[i][k]+dp[k][j])

import java.util.*;
public class Main{
    static int N;
    static int M;
    static int[][] distance;
    public static void floyd() {
        for (int k = 1; k <= N; k++) {
            for (int i = 1; i <= N; i++) {
                for (int j = 1; j <= N; j++) {
                    distance[i][j] = Math.min(distance[i][j], distance[i][k] + distance[k][j]);
                }
            }
        }
        for (int i = 1; i <= N; i++) {
            for (int j = 1; j <= N; j++) {
                System.out.print(distance[i][j] + " ");
            }
            System.out.println();
        }
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N = scanner.nextInt();
        M = scanner.nextInt();
        distance = new int[N + 1][N + 1];
        //初始化
        for (int i = 1; i < N + 1; i++) {
            Arrays.fill(distance[i], Integer.MAX_VALUE);
        }
        for (int i = 1; i <= N; i++) {
            distance[i][i] = 0;
        }
        //输入数据
        for (int i = 0; i < N; i++) {
            int from = scanner.nextInt();
            int to = scanner.nextInt();
            int value = scanner.nextInt();
            distance[from][to] = value;
            distance[to][from] = value;
        }
        floyd();
    }
}

5.SPFA

作用:
1.用于求带负权的边的单元最短路径,但是不能有负环
2.可以用来判断是否存在负环
判断负环:给每一个节点记录一个count,用来记录该结点入队的次数,如果count>n就说明存在负环

import java.util.*;
public class SPFA {
    static class Edge {
        int next;
        int value;
        public Edge(int next, int value) {
            this.next = next;
            this.value = value;
        }
    }
    static int[] distance;
    static int[] count;
    static HashMap<Integer, LinkedList<Edge>> graph = new HashMap<>();
    static int N;
    static int M;
    public static void addNode(int node) {
        if (!graph.containsKey(node)) {
            graph.put(node, new LinkedList<>());
        }
    }
    public static void addEdge(int from, int to, int value) {
        if (!graph.containsKey(from)) {
            addNode(from);
        }
        if (!graph.containsKey(to)) {
            addNode(to);
        }
        graph.get(from).add(new Edge(to, value));
        graph.get(to).add(new Edge(from, value));
    }
    public static void spfa(int start) {
        Arrays.fill(distance, Integer.MAX_VALUE);
        distance[start] = 0;
        LinkedList<Integer> queue = new LinkedList<>();
        queue.add(start);
        count[start] = 1;
        while (!queue.isEmpty()) {
            //1.出队
            int node = queue.removeFirst();
            LinkedList<Edge> edges = graph.get(node);
            //2.遍历所有出边
            for (Edge edge : edges) {
                int next = edge.next;
                int value = edge.value;
                //3.如果可以更新则更新
                if (distance[next] > distance[node] + value) {
                    distance[next] = distance[node] + value;
                    //4.发现没在队列里面就添加进去
                    if (!queue.contains(next)) {
                        queue.add(next);
                        //5.判断是否有负环
                        count[next]++;
                        if (count[next] > N) {
                            System.out.println("存在负环");
                            return;
                        }
                    }
                }
            }
        }
        System.out.println(Arrays.toString(distance));
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N = scanner.nextInt();
        M = scanner.nextInt();
        for (int i = 0; i < M; i++) {
            int from = scanner.nextInt();
            int to = scanner.nextInt();
            int value = scanner.nextInt();
            addEdge(from, to, value);
        }
        distance = new int[N + 1];
        count = new int[N + 1];
        spfa(1);
    }
}

6.拓扑排序

拓扑排序只能用于有向无环图。如果程序结束,inDegree数字中存在非零元素,说明存在环路。

import java.util.*;
public class Main {
    public static HashMap<Integer, LinkedList<Integer>> graph = new HashMap<>();
    static int N;
    static int M;
    static int[] inDegree;
    static boolean[] visited;
    public static void addEdge(int from, int to) {
        if (!graph.containsKey(from)) {
            graph.put(from, new LinkedList<>());
        }
        if (!graph.containsKey(to)) {
            graph.put(to, new LinkedList<>());
        }
        graph.get(from).add(to);
    }
    public static void topo() {
        //如果要求字典序最小,那么可以使用优先队列实现
        LinkedList<Integer> queue = new LinkedList<>();
        for (int i = 1; i <= N; i++) {
            if (inDegree[i] == 0) {
                queue.add(i);
            }
        }
        while (!queue.isEmpty()) {
            int node = queue.removeFirst();
            System.out.print(node+" ");
            LinkedList<Integer> edges = graph.get(node);
            for (Integer edge : edges) {
                inDegree[edge]--;
                if (inDegree[edge]==0){
                    queue.add(edge);
                }
            }
        }
        System.out.println(Arrays.toString(inDegree));
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N = scanner.nextInt();
        M = scanner.nextInt();
        inDegree = new int[N + 1];
        visited = new boolean[N + 1];
        for (int i = 0; i < M; i++) {
            int from = scanner.nextInt();
            int to = scanner.nextInt();
            inDegree[to]++;
            addEdge(from, to);
        }
        topo();
    }
}

7.欧拉回路与欧拉回路(有向图)

欧拉回路:是指通过图的每一条边恰好一次,且能回到起点的路径。要求起始点和终点一致。
对于一个有向图来说:
①如果每个节点都有入度-出度=0,则该图存在欧拉回路

欧拉路径:是指通过图的每一条边恰好一次,但不要求能回到起点的路径。不要求起始点和终点一致。
对于一个有向图来说:
①如果每个节点都有入度-出度=0,那么该图一定存在欧拉路径;
②如果只有一个节点入度-出度=1,只有一个入度-出度=-1,其余结点入度-出度=0,那么该图一定存在欧拉路径;

注意:如果有欧拉回路,那么求出的欧拉路径就是欧拉回路

public class Main {
    static HashMap<Integer, LinkedList<Integer>> graph = new HashMap<>();
    static int N;
    static int M;
    //对于有向图而言
    static int[] inDegree;
    static int[] outDegree;
    //记录欧拉路径的起点,如果有欧拉回路的话,起点无所谓,就当做1
    static int oula_start = 1;
    static int oula_end = 1;
    //记录欧拉路径
    //如果有欧拉回路,那么求得的欧拉路径就是欧拉回路
    static LinkedList<Integer> path = new LinkedList<>();
    static void addEdge(int from, int to) {
        if (!graph.containsKey(from)) {
            graph.put(from, new LinkedList<>());
        }
        if (!graph.containsKey(to)) {
            graph.put(to, new LinkedList<>());
        }
        graph.get(from).add(to);
    }
    static boolean have() {
        int count_fu1 = 0;
        int count_1 = 0;
        boolean result = false;
        for (int i = 1; i <= N; i++) {
            if (inDegree[i] - outDegree[i] == 1) {
                count_1++;
                oula_end = i;
            } else if (inDegree[i] - outDegree[i] == -1) {
                oula_start = i;
                count_fu1++;
            }
        }
        if ((count_fu1 == 0 && count_1 == 0)) {
            System.out.println("有欧拉回路");
            result = true;
        } else if (count_fu1 == 1 && count_1 == 1) {
            System.out.println("没有欧拉回路,有欧拉路径");
            System.out.println("起点为" + oula_start);
            System.out.println("终点为" + oula_end);
            result = true;
        } else {
            System.out.println("没有欧拉路径");
        }
        return result;
    }
    public static void findPath(int node) {
        LinkedList<Integer> edges = graph.get(node);
        while (!edges.isEmpty()) {
            int next = edges.get(0);
            edges.remove(Integer.valueOf(next));
            findPath(next);
        }
        path.add(node);
    }
    //有向图
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N = scanner.nextInt();
        M = scanner.nextInt();
        inDegree = new int[N + 1];
        outDegree = new int[N + 1];
        for (int i = 0; i < M; i++) {
            int from = scanner.nextInt();
            int to = scanner.nextInt();
            addEdge(from, to);
            outDegree[from]++;
            inDegree[to]++;
        }
        //1.判断是否有欧拉路径
        have();
        //2.求解欧拉路径
        //无向图求出的路径可以直接打印,有向图求出的路径需要逆序输出
        findPath(oula_start);
        //3.判断最后的边数
        if (path.size() == M + 1) {
            //逆序输出
            for (int i = path.size() - 1; i >= 0; i--) {
                System.out.print(path.get(i) + " ");
            }
        } else {
            System.out.println("没有欧拉路径");
        }
    }
}

8.欧拉回路和欧拉路径(无向图)

欧拉回路:是指通过图的每一条边恰好一次,且能回到起点的路径。要求起始点和终点一致
对于一个无向图来说:
①如果每个节点的度数都是偶数,则该图存在欧拉回路;

欧拉路径:是指通过图的每一条边恰好一次,但不要求能回到起点的路径。不要求起始点和终点一致.
对于一个无向图来说:
①如果每个节点的度数都是偶数,那么该图一定存在欧拉路径;
②如果只有两个度数为奇数的节点,那么该图一定存在欧拉路径;

public class Main {
    static HashMap<Integer, LinkedList<Integer>> graph = new HashMap<>();
    static int N;
    static int M;
    static int oula_strat = 1;
    static int oula_end = 1;
    static int[] Degree;
    static LinkedList<Integer> path = new LinkedList<>();
    static void addEdge(int from, int to) {
        if (!graph.containsKey(from)) {
            graph.put(from, new LinkedList<>());
        }
        if (!graph.containsKey(to)) {
            graph.put(to, new LinkedList<>());
        }
        graph.get(from).add(to);
        graph.get(to).add(from);
    }
    public static void have() {
        boolean first = true;
        int odd_count = 0;
        for (int i = 1; i <= N; i++) {
            if (Degree[i] % 2 != 0) {
                odd_count++;
                if (first) {
                    oula_strat = i;
                    first = false;
                } else {
                    oula_end = i;
                }
            }
        }
        if (odd_count == 0) {
            System.out.println("存在欧拉回路");
        } else if (odd_count == 2) {
            System.out.println("不存在欧拉回路,但存在欧拉路径,起点为:" + oula_strat + ",终点为:" + oula_end);
        } else {
            System.out.println("不存在欧拉路径");
        }
    }
    static void findPath(int node) {
        LinkedList<Integer> edges = graph.get(node);
        while (!edges.isEmpty()) {
            int next = edges.get(0);
            //注意无向图这里,需要删除两次边
            edges.remove(Integer.valueOf(next));
            graph.get(next).remove(Integer.valueOf(node));

            findPath(next);
        }
        //注意:是在回溯之前加入
        path.add(node);
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N = scanner.nextInt();
        M = scanner.nextInt();
        Degree = new int[N + 1];
        for (int i = 0; i < M; i++) {
            int from = scanner.nextInt();
            int to = scanner.nextInt();
            addEdge(from, to);
            Degree[from]++;
            Degree[to]++;
        }
        //1.判断是否存在欧拉回路或者欧拉路径
        have();
        //2.找欧拉回路或者欧拉路径
        findPath(oula_strat);
        //3.判断最后的边数
        if (path.size() == M + 1) {
            System.out.println(path.toString());
        } else {
            System.out.println("没有欧拉路径");
        }
    }
}

9.最小生成树

import java.util.Arrays;
import java.util.Comparator;

class Edge {
    int src, dest, weight;

    public Edge(int src, int dest, int weight) {
        this.src = src;
        this.dest = dest;
        this.weight = weight;
    }
}

public class KruskalMST {
    private int V; // 图中节点的个数
    private Edge[] edges; // 图中所有边

    // 并查集类
    static class UnionFind {
        int[] parent, rank;

        public UnionFind(int n) {
            parent = new int[n];
            rank = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
                rank[i] = 0;
            }
        }

        public int find(int x) {
            if (parent[x] != x) {
                parent[x] = find(parent[x]); // 路径压缩
            }
            return parent[x];
        }

        public void union(int x, int y) {
            int rootX = find(x);
            int rootY = find(y);

            if (rootX != rootY) {
                if (rank[rootX] > rank[rootY]) {
                    parent[rootY] = rootX;
                } else if (rank[rootX] < rank[rootY]) {
                    parent[rootX] = rootY;
                } else {
                    parent[rootY] = rootX;
                    rank[rootX]++;
                }
            }
        }
    }

    // 构造函数
    public KruskalMST(int V, Edge[] edges) {
        this.V = V;
        this.edges = edges;
    }

    // 生成最小生成树
    public void kruskal() {
        Edge[] result = new Edge[V - 1]; // 最小生成树的边
        int e = 0; // 结果数组的索引
        int i = 0; // 排序后的边的索引

        // 按边权值从小到大排序
        Arrays.sort(edges, Comparator.comparingInt(edge -> edge.weight));

        // 创建并初始化并查集
        UnionFind uf = new UnionFind(V);

        // 选择权值最小的边,依次加入最小生成树
        while (e < V - 1) {
            Edge nextEdge = edges[i++];

            int x = uf.find(nextEdge.src);
            int y = uf.find(nextEdge.dest);

            // 如果加入这条边不会形成环
            if (x != y) {
                result[e++] = nextEdge;
                uf.union(x, y);
            }
        }

        // 输出最小生成树
        System.out.println("Edges in the constructed MST:");
        for (i = 0; i < e; i++) {
            System.out.println(result[i].src + " -- " + result[i].dest + " == " + result[i].weight);
        }
    }

    public static void main(String[] args) {
        int V = 4; // 图中节点的个数
        Edge[] edges = {
            new Edge(0, 1, 10),
            new Edge(0, 2, 6),
            new Edge(0, 3, 5),
            new Edge(1, 3, 15),
            new Edge(2, 3, 4)
        };

        KruskalMST kruskal = new KruskalMST(V, edges);
        kruskal.kruskal(); // 执行克鲁斯卡尔算法
    }
}

四、高级数据结构

1.树状数组

求前n项和,支持在线修改

	public static int getSum(int i) {
        int sum = 0;
        for (int j = i; j >= 1; j -= lowbit(j)) {
            sum += tree[j];
        }
        return sum;
    }

    public static void update(int i, int update) {
        for (int j = i; j <= n; j += lowbit(j)) {
            tree[j] += update;
        }
    }

    public static int lowbit(int num) {
        return num & -num;
    }

2.ST表

作用是:快速进行区间查询,ST表创建 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n)),查询 O ( 1 ) O(1) O(1),不支持在线修改

public class ST{
    static int[] arr;
    static int n;
    static int[][] dp;
    //nlog(n)
    public static void createST() {
        int max = (int) (Math.log(n) / Math.log(2));
        dp = new int[n + 1][max + 1];
        //自己到自己的最值就是自己
        for (int i = 1; i < n; i++) {
            dp[i][0] = arr[i];
        }
        for (int j = 1; j <= max; j++) {
            for (int i = 1; i + (1 << j) - 1 <= n; i++) {
                dp[i][j] = Math.max(dp[i][j - 1], dp[i + (1 << (j - 1)) + 1][j - 1]);
            }
        }
    }
    //o(1)
    public static int query(int l, int r) {
        int max = (int) (Math.log(l - r + 1) / Math.log(2));
        return Math.max(dp[l][max], dp[r - (1 << max) + 1][max]);
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        arr = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            arr[i] = scanner.nextInt();
        }

    }
}

3.线段树

//区间和
import java.util.Scanner;
public class Main {
    static int[] a;
    static int n;
    static Node[] tree;
    static class Node {
        int l;
        int r;
        int num;
        int lazy;//用于记录对于这个节点进行过什么操作

        public Node(int l, int r, int num) {
            this.l = l;
            this.r = r;
            this.num = num;
        }
    }
    public static void build(int index, int l, int r) {
        if (l == r) {
            tree[index] = new Node(l, r, a[l]);
            return;
        }
        int mid = (l + r) / 2;
        build(index * 2, l, mid);
        build(index * 2 + 1, mid + 1, r);
        tree[index] = new Node(l, r, tree[index * 2].num + tree[index * 2 + 1].num);
    }
    //单点修改
    public static void update(int index, int i, int newnum) {
        if (tree[index].l == tree[index].r && tree[index].r == i) {
            tree[index].num = newnum;
            return;
        }
        int mid = (tree[index].l + tree[index].r) / 2;
        if (i <= mid) {
            update(index * 2, i, newnum);
        } else {
            update(index * 2 + 1, i, newnum);
        }
        tree[index].num = tree[index * 2].num + tree[index * 2 + 1].num;
    }
    //区间修改:给区间每个值加d
    public static void change(int index, int l, int r, int d) {
        if (l <= tree[index].l && tree[index].r <= r) {
            tree[index].num += (tree[index].r - tree[index].l + 1) * d;
            tree[index].lazy += d;
            return;
        }
        spred(index);
        int mid = (tree[index].l + tree[index].r) / 2;
        if (l <= mid) {
            change(index * 2, l, r, d);
        }
        if (r > mid) {
            change(index * 2 + 1, l, r, d);
        }
        tree[index].num = tree[index * 2].num + tree[index * 2 + 1].num;
    }
    //一共5个步骤
    public static void spred(int index) {
        if (tree[index].lazy != 0) {
            tree[index * 2].num += (tree[index * 2].r - tree[index * 2].l + 1) * tree[index].lazy;
            tree[index * 2 + 1].num += (tree[index * 2 + 1].r - tree[index * 2 + 1].l + 1) * tree[index].lazy;
            tree[index * 2].lazy += tree[index].lazy;
            tree[index * 2 + 1].lazy += tree[index].lazy;
            tree[index].lazy = 0;
        }
    }

    public static int ask(int index, int l, int r) {
        if (l <= tree[index].l && tree[index].r <= r) {
            return tree[index].num;
        }
        spred(index);
        int sum = 0;
        int mid = (tree[index].l + tree[index].r) / 2;
        if (l <= mid) {
            sum += ask(index * 2, l, r);
        }
        if (r > mid) {
            sum += ask(index * 2 + 1, l, r);
        }
        return sum;
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        n = scanner.nextInt();
        a = new int[n + 1];
        tree = new Node[n * 4];
        int m = scanner.nextInt();
        for (int i = 1; i <= n; i++) {
            a[i] = scanner.nextInt();
        }
        build(1, 1, n);
        for (int i = 0; i < m; i++) {
            int caozuo = scanner.nextInt();
            if (caozuo == 1) {
                int x = scanner.nextInt();
                int y = scanner.nextInt();
                int k = scanner.nextInt();
                change(1, x, y, k);
            } else {
                int x = scanner.nextInt();
                int y = scanner.nextInt();
                System.out.println(ask(1, x, y));
            }
        }
    }
}

4.并查集

public class Main {
    // 数组father用于存储每个节点的父节点
    static int[] father;
    // 数组rank用于存储每个集合的秩(树的高度)
    static int[] rank;

    // 初始化方法,用于初始化并查集
    public static void init(int n) {
        father = new int[n];
        rank = new int[n];
        // 初始化时,每个节点的父节点指向自己,秩为1
        for (int i = 0; i < n; i++) {
            father[i] = i;
            rank[i] = 1;
        }
    }

    // 查找操作,带路径压缩
    public static int find(int x) {
        if (father[x] == x) {
            return x; // 如果x的父节点是自己,返回x
        } else {
            father[x] = find(father[x]); // 递归查找,并进行路径压缩
            return father[x]; // 返回根节点
        }
    }

    // 合并操作,按秩合并
    public static void union(int x, int y) {
        int father_x = find(x); // 查找x的根节点
        int father_y = find(y); // 查找y的根节点

        if (father_x == father_y) return; // 如果两个节点的根节点相同,说明已经连通,直接返回

        // 按秩合并,将秩小的树挂在秩大的树下
        if (rank[father_x] < rank[father_y]) {
            father[father_x] = father_y; // x的根节点指向y的根节点
        } else if (rank[father_x] > rank[father_y]) {
            father[father_y] = father_x; // y的根节点指向x的根节点
        } else {
            father[father_x] = father_y; // 如果秩相同,随意合并,并增加新根的秩
            rank[father_y]++;
        }
    }

    // 主方法,测试并查集功能
    public static void main(String[] args) {
        init(5); // 创建一个包含5个元素的并查集
        union(0, 1); // 合并0和1
        union(1, 2); // 合并1和2
        System.out.println(find(0) == find(2)); // 输出true,因为0和2是连通的
        union(3, 4); // 合并3和4
        System.out.println(find(0) == find(4)); // 输出false,因为0和4不连通
    }
}


五、动态规划

1.数位dp

public class Main {
    static long[] limit;
    static int length;
    static long[][] dp;

    public static long dfs(int pos, int pre, boolean flag, boolean lead) {
        if (pos == length) return 1;
        if (!flag && !lead && dp[pos][pre] != -1) return dp[pos][pre];
        long max = (flag ? limit[pos] : 进制数);
        long sum = 0;
        for (int i = 0; i <= max; i++) {
            if (条件) {
                sum += dfs(pos + 1, i, flag && i == limit[pos], lead && i == 0);
            }
        }
        if (!flag && !lead) dp[pos][pre] = sum;
        return sum;
    }

    public static long solve(long num) {
        if (num == 0) return 1;
        length = ("" + num).length();
        limit = new long[length];
        dp = new long[length][10];
        for (int i = 0; i < length; i++) {
            Arrays.fill(dp[i], -1);
        }
        for (int i = length - 1; i >= 0; i--) {
            limit[i] = num % 进制数;
            num /= 进制数;
        }
        return dfs(0, 0, true, true);
    }
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int a = scanner.nextInt();
        int b = scanner.nextInt();
        System.out.println(solve(b) - solve(a - 1));
    }
}

2.状态压缩dp

这一部分没有统一的算法模板,只有对二进制操作熟悉之后才能做好这类题,所以我在此总结几个操作。

进行标记:i | (1<<j)
判断是否被标记: (i & (1<<j))==0
判断是否包含:a | b == b
当前行选的元素不能相邻:now & (now >> 1) == 0
当前行与上一行的选择上下也不能相邻:pre & now == 0
注意:添加状态用 | ,删除状态用 ^,判断状态用 &

3.最长公共子序列

public class Main {
    public static void main(String[] args) {
        char[] str1 = "abcdefgh".toCharArray();
        char[] str2 = "acjlfabhh".toCharArray();
        int[][] dp = new int[str1.length + 1][str2.length + 1];
        for (int i = 1; i <= str1.length; i++) {
            for (int j = 1; j <= str2.length; j++) {
                if (str1[i - 1] == str2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        System.out.println(dp[str1.length][str2.length]);
    }
}

4.最大连续子序列和

public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{-2, 11, -4, 13, -5, -2};
        int[] dp = new int[arr.length];
        dp[0] = arr[0];
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            dp[i] = Math.max(dp[i - 1] + arr[i], arr[i]);
            max = Math.max(max, dp[i]);
        }
        System.out.println(max);
    }
}

5.最长上升子序列

public class Main {
    public static void main(String[] args) {
        int[] arr = new int[]{2, 1, 5, 3, 6, 4, 6, 3};
        int[] dp = new int[arr.length];
        dp[0] = 1;
        int max = dp[0];
        for (int i = 1; i < arr.length; i++) {
            for (int j = i - 1; j >= 0; j--) {
                if (arr[i] > arr[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
                max = Math.max(max, dp[i]);
            }
        }
        System.out.println(max);

    }
}

六、其他

1.二分答案

这是求最小值

while (l < r) {
      int mid = (r + l) / 2;
      if (check(mid)) {
          r = mid;
      } else {
          l = mid + 1;
      }
}

这是求最大值

while (l < r) {
      int mid = (r + l + 1) / 2;
      if (check(mid)) {
          l = mid;
      } else {
          r = mid - 1;
      }
}

2.数据离散化

由于原数组的值域很大,所以需要离散化。
原数组:[2,87,98,15,3,1,9999,55,98]
离散化:[2,6,7,4,3,1,8,5,7]
离散化后的数组:存放每个对应位置的数在原数组中排第几,这样每个数对应的大小顺序没有变化,但是值域小了。

	public static int[] convert(int[] nums) {
		//使用set去重
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            set.add(nums[i]);
        }
        //排序
        Integer[] copy_nums = set.toArray(new Integer[0]);
        Arrays.sort(copy_nums);
        //进行二分查找:离散化
        int[] new_nums = new int[nums.length];
        for (int i = 0; i < new_nums.length; i++) {
            new_nums[i] = Arrays.binarySearch(copy_nums, nums[i]) + 1;
        }
        return new_nums;
    }

3.求逆序对数(采用树状数组)

public class Main {
    public static int[] convert(int[] nums) {
        HashSet<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            set.add(nums[i]);
        }
        Integer[] copy_nums = set.toArray(new Integer[0]);
        Arrays.sort(copy_nums);
        int[] new_nums = new int[nums.length];
        for (int i = 0; i < new_nums.length; i++) {
            new_nums[i] = Arrays.binarySearch(copy_nums, nums[i]) + 1;
        }
        tree = new int[set.size() + 1];
        return new_nums;
    }
    public static int lowbit(int num) {
        return num & -num;
    }
    public static void update(int index, int value) {
        for (int i = index; i < tree.length; i += lowbit(i)) {
            tree[i] += value;
        }
    }
    public static long getSum(int index) {
        long result = 0;
        for (int i = index; i >= 1; i -= lowbit(i)) {
            result += tree[i];
        }
        return result;
    }
    static int[] tree;
    public static void main(String[] args) {
        int[] nums = new int[]{1, 2, 3, 4, 5};
        int[] new_nums = convert(nums);
        int ans = 0;
        for (int i = 0; i < new_nums.length; i++) {
            update(new_nums[i], 1);
            ans += (i + 1) - getSum(new_nums[i]);
        }
        System.out.println(ans);
    }
}

4.康托展开(蓝桥杯例题:排列距离)

import java.util.*;
public class Main {
    static char[] arr = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'n', 'o', 'p', 'q', 'r'};
    static HashMap<Character, Integer> map = new HashMap<>();
    //将字符串转为对应的排列
    public static int[] getPailie(String str) {
        int[] result = new int[17];
        for (int i = 0; i < result.length; i++) {
            result[i] = map.get(str.charAt(i));
        }
        return result;
    }
    public static long jiecheng(int num) {
        if (num == 0) return 1;
        if (num == 1) return 1;
        return num * jiecheng(num - 1);
    }
    //获取康托排名
    public static long kangtuo(int[] pailie) {
        //开始计算排名
        long paiming = 1;
        for (int i = 0; i < pailie.length; i++) {
            int count = 0;//记录有几个比当前的数字小的个数
            //注意j是从i的后面找
            for (int j = i + 1; j < pailie.length; j++) {
                if (pailie[j] < pailie[i]) {
                    count++;
                }
            }
            paiming += count * jiecheng(pailie.length - i - 1);
        }
        return paiming;
    }
    public static void main(String[] args) {
        for (int i = 0; i < arr.length; i++) {
            map.put(arr[i], i + 1);
        }
        long A_paiming = kangtuo(getPailie("aejcldbhpiogfqnkr"));
        long B_paiming = kangtuo(getPailie("ncfjboqiealhkrpgd"));
        long ans = Math.min(B_paiming - A_paiming, jiecheng(17) - B_paiming + A_paiming);
        System.out.println(ans);
    }
}

5.逆康托展开

public class Main {
    static int n = 5;
    static long[] jiecheng = new long[n + 1];
    static boolean[] used = new boolean[n + 1];
    public static String get(long num) {
        num--;
        StringBuilder result = new StringBuilder();
        for (int i = n - 1; i >= 0; i--) {
            //从没有确定的数中选择第value+1大的数
            int value = (int) (num / jiecheng[i]);
            num = num % jiecheng[i];
            int count = 0;
            for (int j = 1; j <= n; j++) {
                if (!used[j]) {
                    count++;
                    if (count == value + 1) {
                        result.append(j);
                        used[j] =true;
                        break;
                    }
                }
            }
        }
        return result.toString();
    }
    public static void getjiecheng(int n) {
        jiecheng[0] = 1;
        jiecheng[1] = 1;
        for (int i = 2; i <= n; i++) {
            jiecheng[i] = i * jiecheng[i - 1];
        }
    }
    public static void main(String[] args) {
        getjiecheng(n);
        System.out.println(get(3));
    }
}

6.斐波那契规律

a 1 + . . . + a n = a n + 2 − 1 a_1+...+a_n=a_{n+2}-1 a1+...+an=an+21

a 1 2 + . . . + a n 2 = a n ∗ a n + 1 a_1^2+...+a_n^2=a_n*a_{n+1} a12+...+an2=anan+1

a 1 + a 3 + . . . + a 2 n − 1 = a 2 n a_1+a_3+...+a_{2n-1}=a_{2n} a1+a3+...+a2n1=a2n

a 2 + a 4 + . . . + a 2 n = a 2 n + 1 − 1 a_2+a_4+...+a_{2n}=a_{2n+1}-1 a2+a4+...+a2n=a2n+11

每一项模一个数的余数是有周期性的

7.坐标变换

一般使用一个数组来进行方向转换

//表示方向
static int[] dx = new int[]{-1, 1, 0, 0};
static int[] dy = new int[]{0, 0, -1, 1};
for (int i = 0; i < 4; i++) {
    int new_x = x + dx[i];
    int new_y = y + dy[i];
}

8.数列

等差数列:
通项公式: a n = a 1 + ( n − 1 ) d a_n=a_1+(n-1)d an=a1+(n1)d
前n项目和: S n = n ( a 1 + a n ) 2 = n a 1 + n ( n − 1 ) d 2 S_n=\frac{n(a_1+a_n)}{2}=na_1+\frac{n(n-1)d}{2} Sn=2n(a1+an)=na1+2n(n1)d

等比数列:
通项公式: a n = a 1 q n − 1 a_n=a_1q^{n-1} an=a1qn1
前n项目和: S n = a 1 ( 1 − q n ) 1 − q = a 1 − a n q 1 − q S_n=\frac{a_1(1-q^n)}{1-q}=\frac{a_1-a_nq}{1-q} Sn=1qa1(1qn)=1qa1anq

9.错排数

选出一个数a,可以放的位置有 n − 1 n-1 n1个,假如放在了b的位置,那么b有如下的两种选择:
①放在a的位置,那么问题就变为剩下的 n − 2 n-2 n2个元素的错排问题
②如果不放a的位置,那么每个元素可以选的位置都是 n − 2 n-2 n2个,相当于为 n − 1 n-1 n1个元素的错排问题

public static int count(int n) {
    if (n == 1) return 0;
    if (n == 2) return 1;
    return (n - 1) * (count(n - 1) + count(n - 2));
}

10.组合数

C 10 3 = 10 × 9 × 8 1 × 2 × 3 {\large C} _{10}^{3} =\frac{10\times 9\times8}{1\times 2\times3} C103=1×2×310×9×8

分子从大到小,分母从小到大

public static long zuhe(long n, long m) {
        if (m == 0) return 1;
        if (n == m) return 1;
        if (m > n / 2) {
            m = n - m;
        }
        long result = 1;
        for (long i = n, j = 1; j <= m; i--, j++) {
            result = result * i / j;
        }
        return result;
    }

11.相关经验(持续更新)

1.看到最大最小,要想到二分法。
2.看见区间,要想到ST表,线段树。
3.看见前缀和,想到树状数组。
4.多决策问题,首先想到dfs,再看能不能剪枝优化,如果能套上dp,就用dp。


希望这篇博客文章对你有所帮助!

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值