算法基础复习(数据结构+蓝桥杯备战)(二)

2.简单算法

递归:

递归

递归的含义很好理解,就是一个函数调用自身,难就难在如何确定一个题目的递归式–这就需要多刷题了

一个完整的递归函数包括两个部分

  • 1.递归式
  • 2.递归入口

以斐波那契数列为例:

int f(int n){

if(n1||n2) return 1; //出口

return f(n-1)+f(n-2); //递归式

}

递归式用来递归计算我们想要得到的值,递归出口用来结束递归

什么题用到递归

子问题和原文题的求解方式完全相同的时候可以用递归

举个例子

蓝桥杯练习习通 计算n阶行列式公式


计算n阶行列式

给定一个N×N的矩阵A,求|A|。

输入格式:
第一行一个正整数N。 接下来N行,每行N个整数,
第i行第j个数字表示A[i][j]。

输出格式 一行,输出|A|。


A11A12…A1n
A21A22…A2n

An1An2…Ann

n==2 : ∣ A ∣ = A 11 ∗ A 22 − A 12 ∗ 21

A B C
D E F = A * E F + B * D F + C * D E
G H I H I G I G H

所以当n == 2时就是递归出口了, n > 2 递归计算。

import java.io.*;
public class 计算行列式{
	static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
	
	public static int Int(String s){return Integer.parseInt(s);}
	
	public static void copy(int[][]A, int[][] A1, int i, int len) throws IOException{
		for(int x = 1; x < len; x++)
			for(int y = 0, j = 0; j < len; j++)
				if(j != i) {
					A1[x-1][y++] = A[x][j];
				}
	}
	public static int F(int[][] A, int len)throws Exception{
		int res = 0; //保存每个矩阵的|A|
		if(len == 1)return A[0][0];
		if(len == 2){
			return A[0][0]*A[1][1] - A[0][1]*A[1][0]; // 递归出口
		}
		else{
			int A1[][] = new int[10][10];
			for(int i = 0; i < len; i++){
				copy(A, A1, i, len);
				res += Math.pow(-1, i) * A[0][i] * F(A1, len-1); //递归式
			}
		}
		return res;
	}
	public static void main(String[] args) throws Exception{
		int n;
		n = Integer.parseInt(in.readLine());
		
		int arr[][] = new int[10][10];

		for(int i = 0; i < n; i++){
			String[] s = in.readLine().split(" "); 
			for(int j = 0; j < n; j++){
				arr[i][j] = Int(s[j]);
			}
		}
		out.write(F(arr, n) + "\n");
		out.flush();
	}
}

深度优先搜索(DFS)和广度优先搜索(BFS)

摘要

DFS和BFS是两种搜索树和图的基本策略

DFS常用于暴力搜索所有状态

BFS常用于搜索到某一状态的最短路径

状态

我们将DFS和BFS搜搜的东西称为状态,状态就是一个具体问题的解,而将所有状态关联在一起,就能得到一个状态图,DFS和BDF就是遍历状态图的两种方式

位运算:

1.什么是位运算

位运算又称为位操作,指的是直接对二进制进行的一系列操作

2.位运算有哪些
  • 1.AND(&) 按位与

  • 2.| 按位或

  • 3.按位异或

  • 4.取反(~)

  • 5.移位运算

    • 1.左移运算符:<<(这两个 重点记住) 9>>2=2
    • 2.右移运算符:>>
3.常用的位运算操作

1.(n>>k) &1 取出整数n在二进制表示下的第k位

2.n & ((1 << k) - 1) 取出整数n在二进制表示下的第0~k-1位(后k位)

3.n ^ (1 << k) 把整数n在二进制表示下的第k位取反

4.n | (1 << k) 把整数n在二进制表示下的第k位赋值为1

5.n & (~(1 << k)) 把整数n在二进制表示下的第k位赋值为0

6.n ^ (1 << k) = n - (1<<k)

7.除以2
a / 2 = a >> 1
(a + b) / 2 == a + b >> 1 ( + - 运算的优先级高于 <<, >> )

8.判断奇偶
一个数的二进制数的最低位如果是1 则该数一定是奇数 否则一定是偶数
所以 用 a & 1 检测最低为是否位1

if(a & 1) cout<<"奇数";
else cout<<"偶数" 


  1. 状态压缩以一个二进制数表示一个状态集合。
    如 n = 1100 S = {2, 3} S表示状态所有为1的集合。
  2. 成对变换当n 为偶数时 n ^ 1 = n + 1
    当n为奇数时 n ^ 1 = n - 1
    所以
    (0,1) (2, 3) (4, 5)… 关于 ^1 运算 构成“成对变换”
    这一性质常用于图论邻接表中边集的存储。在具有无向边(双向边)的图中把一对正反方向的边分别存储在邻接表数组中的第n和第n+1位置(n为偶数),就可以通过^1
    的运算获得与当前边(x, y) 反向的边(y, x)的存储位置。

二分查找

摘要

本文主要介绍二分法。 二分法是一种精妙算法,效率高

二分法用在于单调的序列内快速查找某个值,方法是序列分为两半,判断要查找的值在那个区间,舍弃另一半

二分查找

在有序的序列中查找x是否存在

二分查找一个大于等于X的最小值

另外一种解释方式就是在序列中可以正确插入X而不影响序列升序状态的第一个位置

public static int Search(int x){
int l = 0, r = n-1;
	while(l < r){
		int mid = l + r >> 1; 
		
		// mid位置上的元素小于x,我们要查找的是大于等于的
		if(arr[mid] < x)
		{ 
			// 所以mid和其左边的全部舍弃
			l = mid + 1; 
		}
		else
		{ 
	    //mid位置上的元素大于等于x,我们不能舍弃,但要向左偏移,所以令r=mid
		r = mid; 
		}
		
	}                 
	return l; 
}
二分查找一个小于等于x的最大值
public static int Search(int x){
int l = 0, r = n-1;
	while(l < r){
		int mid = l + r + 1 >> 1;  // 注意这里加了1 
		//+1是为了在二分的时候向下取整的尾数不为1
		// mid位置上的元素大于x,我们要查找的是小于等于的
		if(arr[mid] > x)
		{ 
			// 所以mid和其右边的全部舍弃
			r = mid - 1; 
		}
		else
		{ 
	    //mid位置上的元素小于等于x,我们不能舍弃,但要向右偏移,所以令l=mid
		l = mid; 
		}
		
	}                 
	return l; 
}

在以上的代码中, mid = (l + r + 1) / 2
为什么呢? 因为(l+r)/2的结果会被下取整,当l = 0, r = 1时,mid = 0

如果此时arr[0]正好等于x的话, l 就会等于0, 那么因为l和r并没有被更新,
下一次循环mid还是0, 所以就会一直这样循环下去。为了避免死循环,需要特殊处理一下边界问题,也就是(l + r + 1) / 2。这样得到的mid就会不同了。

求X在序列中的起始位置和结束位置

在这里插入图片描述

import java.io.*;
import java.util.*;
public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    static String[] s = new String[100010];
    static int[] arr = new int[100010];
    static int n, q;
    
    public static int findl(int x){
        int l = 0, r = n-1;
        while(l < r){
            int mid = l + r >> 1;
            if(arr[mid] < x) l = mid + 1;
            else r = mid;  
        }
        return l;
    }
      public static int findr(int x){
        int l = 0, r = n-1;
        while(l < r){
            int mid = l + r + 1 >> 1;
            if(arr[mid] > x) r = mid - 1;
            else l = mid;  
        }
        return l;
    }
    
    public static void main(String[] agrs) throws IOException {
        String[] test = in.readLine().split(" ");
        n = Integer.valueOf(test[0]);
        q = Integer.valueOf(test[1]);
        
        s = in.readLine().split(" ");
        for(int i = 0; i < n; i++) arr[i] = Integer.parseInt(s[i]);
        for(int i = 0; i < q; i++){
            int x = Integer.valueOf(in.readLine());
            int l = findl(x);
            int r = findr(x);
            if(x != arr[l]) out.write("-1 -1\n"); //不存在
            else out.write(l + " " + r + "\n");
        }
        out.flush();
    } 
    
    
}

快速排序

快速排序思想

从序列中取一个基准值,然后将序列分为左右两部分,使得左边的数比基准值小,右边的数比基准值大。递归这个过程,直到不可再分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HHOAx2uB-1620523087785)(C:%5CUsers%5C%E4%B8%BF%E5%89%91%E6%9D%A5%C2%B7%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20210415211131275.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AApEBLak-1620523087787)(C:%5CUsers%5C%E4%B8%BF%E5%89%91%E6%9D%A5%C2%B7%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20210415211430021.png)]

import java.io.*;
import java.util.*;
 
public class 快速排序{
 
    static int[] arr = new int[100010];
    
    public static void q_sort(int l,int r){
        if(l>=r)return;
        int i = l - 1;
        int j = r + 1;
        
        int x = arr[l + r >> 1];
        while(i < j){
            do i++;while(arr[i] < x);
            do j--;while(arr[j] > x);
            if(i<j){
                int temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
        q_sort(l,j);
        q_sort(j+1,r);
    }
    
    public static void main(String[] args) throws IOException{
      
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        
        int n  = Integer.parseInt(in.readLine());
        
        String s[] = in.readLine().split(" ");
        for(int i = 0; i < s.length; i++){
            arr[i] = Integer.parseInt(s[i]);
        }
       
        q_sort(0, n-1);
       
        for(int i = 0; i < n; i++){
            System.out.print(arr[i]+" ");
        }
    }
 
}

三 简单数据结构

二十 数组模拟单链表

数组模拟单链表

我们知道链表是通过指针将所有的结点链接实现的,在此过程中,没创建的一个新的接待你,都要给他分配空间,而且要new一下,这个非常耗时,另外,链表用代码实现是很麻烦的

我们可以将数组每个元素当作一个节点,然后当我们呢开辟一个长度为n的数组我们就得到了n个未使用的节点,用数组模拟的链表也被称为静态链表。

怎么实现呢

1.首先你需要一个头指针head。因为用数组模拟,所以我们只知道数组下标就能知道该位置的值为多少。所以head用int存储。

int head =-1;  //刚开始指向为空

2.链表的一个节点需要两个数据:值和next指针,而数组的一个位置只能存 储一个数据,所以,我们还需要两个数组,一个存储值,一个存储 next指针。

int[] data = new int[100010];
int[] next = new int[100010];

3.因为我们需要能够从头结点依次找到所有的结点,所以我们需要给每个结点设置一个编号,这样我们就可以根据编号找到每个结点(相当于地址)。

int idx = 0; // 从数组的0下标开始存储。

4.然后最关键的一步就是插入了,这里我们采用头插法:和指针实现的链表一样,我们需要先把值给存到结点中:

data[idx] = X; // 将X存到data[idx]中

5.然后我们需要将新插入的结点的next指针指向头结点指向的结点。

next[idx] = head; // 注意这里的head存的是头结点下一个结点的地址,相当于head.next
插入
public static void add_head(int x){
	data[idx] = x;
	next[idx] = head;
	head = idx ++;
}
遍历
public static void delete(int x){
	for(int i = head, j = next[head]; i != -1; i = next[i]){
		if(data[i] == x){
			// do something you like
		}
	}
}

二十二 哈希表

摘要

本文主要介绍哈希表的定义,并用数组模拟哈希表

哈希表

哈希表又称为散列表,其原理是每个元素都能通过哈希函数得到一个哈希值,然后改元素在表中存储的下表就是该哈希值,访问的时候也是通过哈希值进行访问的。理论查找时间复杂度是O(1)

计算公式

一般的整数哈希函数: hash(X) = (x%P+P)%P

P一般取大于最大数据量的第一个质数

举个例子:

有一组数,个数为 8,1 3 5 7 13 20 50 101 ,则P取11, 11是大于8的第一个质数。
然后分别求出这8个数的哈希值。
hash(1) = 1
hash(3) = 3
hash(5) = 5
hash(7) = 7
hash(13) = 2
hash(20) = 9
hash(50) = 4
hash(101) = 2

我们发现以上哈希值又两个相同的,101和13放在了同一个位置,这被称为冲突

冲突两种解决办法
链地址发(拉链法)

将数据储存在哈希值对应的单链表中

在这里插入图片描述

题目:模拟散列表

“I x”,插入一个数x;
“Q x”,询问数x是否在集合中出现过;
现在要进行N次操作,对于每个询问操作输出对应的结果。

输入格式

第一行包含整数N,表示操作数量。

接下来N行,每行包含一个操作指令,操作指令为”I x”,”Q x”中的一种。

输出格式

对于每个询问指令“Q x”,输出一个询问结果,如果x在集合中出现过,则输出“Yes”,否则输出“No”。

每个结果占一行。

数据范围

1≤N≤1051≤N≤105
−109≤x≤109−109≤x≤109
输入样例:

5
I 1
I 2
I 3
Q 2
Q 5
输出样例:

Yes
No

代码:

import java.io.*;
import java.util.*;

public class Main{
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    static final int N = 100003; // 假设数据最多为100000个
    static int[] hash = new int[N]; //哈希表表头
    static int[] data = new int[N];  // 所有单链表共用一个数组空间
    static int[] next = new int[N];  // next指针
    static int idx = 1; // static标识的数组是自动初始化为0的所以让
    //结点的编号从 1 开始,这样就不需要对hash初始化为-1了,当指针为0时,则表示为空。
    public static void Insert(int x){
        int k = (x % N + N) % N;
        data[idx] = x;
        next[idx] = hash[k];
        hash[k] = idx;
        idx++;
    }
    public static void find(int x) throws IOException{
        int k = (x % N + N) % N;
        k = hash[k];
        Boolean flag = false; 
        while(k != 0){
            if(data[k] == x){
                flag = true;
                break;
            }
            k = next[k];
        }
        if(flag){
            out.write("Yes\n");
        }
        else out.write("No\n");
        
        out.flush();
    }
    public static void main(String[] agrs)throws IOException {  
        int n = Integer.parseInt(in.readLine());
        for(int i = 0; i < n; i++){
            String s[] = in.readLine().split(" ");
            switch(s[0]){
                case "I" : Insert(Integer.parseInt(s[1])); break;
                case "Q" : find(Integer.parseInt(s[1])); break;
            }       
        }
    }	
}

二十三 并查集

摘要

本文主要介绍并查集和其效率最高并且最简单的实现方法

什么是并查集

并查集顾名思义,是一种用于处理集合和集合至今啊查询和合并等·操作的数据结构,比如询问两点是否在同一集合,将两个不同集合合并

给出N个点划分为两个集合,查询其中某两个点是否在同一集合内

简单的方法就是,将同一个集合内的所有点设置一个标记,然后查询两个点标记是否一样 ——这就是并查集

1.首先,开一个数组,存n个点

int p[N];	

2.刚开始每个点都在不同集合内,所以初始化让每个点指向自己

for(int i = 1; i <= n; i++) p[i] = i; //并查集初始化

3.那么让两个不用的集合合并呢? 很简单但,让其中一个带你指向另一个点

p[a] = b;  // 将a连接到b所在的集合内
或者
//p[b] = a;  //将b连接到a所在的集合内

4.那么问题来了,直接让p[a] = b, 那查询的时候肯定会出错的,因为每个点所指向的点都不一样,前面说了,要将同一个集合内的点指向同一个标记**,所以p[a] 指向的点应该是b的标记,也就是p[b]的父节点。**
也就是p[a] = p[b];但是如果b指向不是那个标记,也就是最终的父节点,这时就又出错了。所以我们要让每个点都指向最终的父结点,也就是那个标记。所以我们要先找到父节点。某一个点指向自己,那么它就是父节点,其他点均指向它。
此find函数是并查集的精髓所在

 public static int find(int x){
    if(p[x] != x) p[x] = find(p[x]);// p[x] != x 说明p[x]指向的不是父节点
    return p[x];                   // 在搜索父节点的同时更新p[x]让其指向父节点
 }

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这时应该让4指向3 而不是否则查询2,4是否在同一个集合肯定会出错的

所以,find很重要,不能写错

四 图论

图和树的存储方式:邻接矩阵和邻接表“
摘要

本文主要介绍邻接矩阵和邻接表的实现方式,无向图和有向图的区别,以及稠密图和稀疏图的区别。以及两个存储方式的使用场景。稠密图使用邻接矩阵存储,稀疏图使用邻接表存储

无向图和有向图的区别

无向图是特殊的有向图 无向图就是两个点只要有边就可以相互到达

稀疏图和稠密图

稀疏图一般指的是一个图中点数和边数是同一数量级的,比如点数是1000,边数也是1000左右的,而稠密图一般指边数是点数的平方,比如点数是100,而边数是10000。

邻接矩阵

说白了就是一个数组

在这里插入图片描述

那么邻接矩阵D表示为:

在这里插入图片描述

其中INF表示正无穷,即该店和另外一个点没有边相连

所以 D[i][j]就是i号点到j号点之间边的长度

邻接矩阵的初始化

因为自己到自己的距离是0,并且如果两点之间没有边相连要设为INF,所以要先将这两种情况给初始化。

假设有一共有n个点,设邻接矩阵为D

for(int i = 1; i <= n; i++){
	for(int j = 1; j <= n; i++){
		if(i == j){
			D[i][j] = 0;
		}
		else{
			D[i][j] = INF; 
		}
	}
}

注意: INF一般设为:0x3f3f3f3f

邻接矩阵读入

重边和自环:

重边就是输入数据中重复给出两个点之间的边

自环就是一个点与自己项链

无向图和有向图的区别:

无向图是特殊的有向图,我们对有向图建边的时候只需要建立一条边,无向图则需要建立正反两条边

有些题目可能存在重边和自环,所以读入边的时候要特判一下。比如求最短路问题时会将最小的边存储起来。

一般的读入格式 : (假设一共有m条边,每行给出3个整数,a,b,w表示a到b的边长为w)

邻接表

邻接表采用的是数组+链表的存储方式,和之前讲过的哈希表用拉链法实现的方式是一模一样的。

图示为:

在这里插入图片描述

所以邻接表就是将一个点的所有出边存储在一条单链表上。

邻接表的实现

邻接表的实现方法有很多种,可以使用二维的Vector(动态数组)来存储每个点的出边。

另外,也可以使用数组模拟单链表的方法,这里推荐使用数组模拟单链表的方法来实现邻接表,原因还是这种方式效率高。

另外,这跟哈希表用拉链法解决冲突是完全一样的。唯一的区别是,再存储边的时候,我们不仅需要存储边的长度还需要存储当前点是和哪个点相连。
另: (静态邻接表又被称为链式前向星)

int [] e = new int[N]; // 表示指向的点
int [] w = new int[N]; // 表示边长
int [] next = new int[N]; // next指针
int [] head = new int[N]; // 邻接表表头
int idx = 1; // 链表结点编号

// 插入
public static void add(int a, int b, int c){ // 将a和b之间建一条边
	e[idx] = b; // 存储a点指向哪个点
	w[idx] = c; // 存储边长
	next[idx] = head[a];
	head[a] = idx++; 
}

// 遍历一个点的所有出边
for(int i = head[a]; i != 0; i = next[i]){
	int b = e[i]; // a点指向的边
	int c = w[i]; // a到b的边长
}

// 无向图的读入, 假如有m条边
for(int i = 0; i < m; i++){
	int a = in.nextInt();
	int b = in.nextInt();
	int c = in.nextInt();
	add(a, b, c);
	add(b, a, c); //因为是无向图所以要反向建边。
}

二十六 最短路问题

摘要

研究最短路的五种算法:

  • 朴素Dijkstra
  • 堆优化Dijkstra
  • Bellman-Ford
  • SPFA
  • Floyd
单源最短路:一个点到其他店的最短路

无负边权时适用算法(所有边长都为正数)

  • 稠密图: 朴素Dijksatra
  • 稀疏图:堆优化Dijkstra, SPFA

有负边权适用算法:

  • SPFA
  • Bellman-Ford

求有变数限制的单源最短路:

  • Bellman-Ford
多源汇最短路: 任意两点间的最短路
  • Floyd
图的存储方式

存储稠密图时,使用邻接矩阵。
存储稀疏图时,使用邻接表。
稠密图是指边数远大于点数,稀疏图是边数和点数差不多相等。
以n表示点数, m表示边数,
一般来说题目数据是n < 100, m < 10000 的图是稠密图, 是n<10000, m<10000的图是稀疏图。

朴素Dijkastra

算法思想:

朴素DijKastra的思想是通过一个距离七点A最近的点B,缩短七点A通过B到达其他点的距离,因为只有通过距离七点最近的点,才有可能缩短起点到达其他店的距离

所以一共分两步

第一步: 找到距离起点最近的点

第二步:通过该点缩短起点到达其他店的距离

在这里插入图片描述

对于AB和AC,AB为3,AC为6,AB是A到其他店的最短距离,如果AC有可能更小的话,那么一定是经过B。原本A到C是6, 但经过B, ABC的距离是4.所以AC的最短距离是4.

实现:

1.我们设置一个dis数组,来表示其他点到起点的最短距离.起点到起点的距离是0,所以dis[1]=0,其他店到起点的距离为正无穷

2.然后,我们要找到一个距离起点最近点.用循环去遍历

int min = INF  //表示没有处理过距离最近的点
  //找出距离起点最近的点
  for(int j=1;j<=n;j++){
    if(st[j]==0&&st[min]>st[j]){
      min = j;
    }
  }

所以min就是我们找到的还没处理过的距离起点最近的点

处理的意思就是已经确定该点的最短路径

然后通过min这个点去缩点起点到达其他点的距离

for(int j =1; i<=n; j++){
  dis[j] = max(dis[j],dis[j]+w[min][j])
}

核心代码:

public static int Dijkstra(){
       Arrays.fill(dis, 0x3f3f3f3f);
        dis[1] = 0;
        for(int i = 0; i < n; i++){ // 循环n次, 每次找出一个离起点最近的点。
            int t = -1;
            for(int j = 1; j <= n; j++){
                if(S[j] == 0 && (t == -1 || dis[t] > dis[j])){ // 找到一个距离最短的加入S集合。
                    t = j;
                }
            }
            
            S[t] = 1;  // 然后通过这个点来缩短原点到达其他点的距离。
            for(int j = 2; j <= n; j++){
                dis[j] = Math.min(dis[j], dis[t] + w[t][j]);
            }
        }    
        
        if(dis[n] != 0x3f3f3f3f) return dis[n];
        else return -1;
    }  

题目案例:

五 数学实战

判断一个数是否为质数

我们只需要判断[2,根号n] 内是否含有n的银子,如果有则n为合数,否则,则为质数

public static Boolean isprime(int n){
    if(n == 1) return false;
    for(int i = 2; i <= n / i; i++){
        if(n % i == 0){
            return false;
        }
    }
    return true;
}

分解质因数

根据算术基本定理又称唯一分解定理,对于任何一个合数, 我们都可以用几个质数的幂的乘积来表示。
即: N = p 1 k 1 ∗ p 2 k 2 ∗ . . . ∗ p n k n N = p_1{k_1}*p_2{k_2}*… *p_n^{k_n}N=p

如:
12 = 2 2 ∗ 3 12 =2^2312=2
2
∗3
20 = 2 2 ∗ 5 20 = 2^2
520=2
2
∗5
30 = 2 ∗ 3 ∗ 5 30 = 23530=2∗3∗5

接下来我们利用这个公式分解质因数。
设一个质数为p.如果n%p == 0,那么p就是n的一个质因数,接下来就是求p的指数,我们让n = n/p, 这样就从n中剔除了一个p,接着重复上述两步,直到n%p != 0

三十 素数筛法

摘要

埃氏筛法 欧拉筛法

现在求区间[1,e7]所有质数

埃氏筛法

三十一 快速幂

求n的k次方

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ljy5p19b-1620523087790)(C:%5CUsers%5C%E4%B8%BF%E5%89%91%E6%9D%A5%C2%B7%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20210417110937273.png)]

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值