(新版)SJTU-OJ-1028. 采购

题目描述

skytim是伟大帝国UDF的首脑。作为一国的领袖,skytim很早就认识到一国的交通对经济建设的重要性。于是他要求UDF国内的建筑排成m*n的网络,道路建设在建筑的上方,并且每条道路都连接着两个有公共边的建筑。如下图所示:


这天是UDF国建国110周年纪念日,skytim正在主持盛大的阅兵式。随着“同志们好!”“首长好!”的声响此起彼伏,skytim的手机突然响了,是他的夫人Cyning打来的。

“skytim你早饭煮烂了,快给我回来!”

“啊?我还在阅兵呢!”

“我不管你现在在哪里,现在马上给我回来!”

“嗯,我现在马上回去!”

“回来记得顺路帮我买一把油纸伞。我要撑着它走过那条雨巷。好想知道成为戴望舒笔下那个丁香一样的结着愁怨的姑娘是一种怎样的体验。”

“行行行,随你。”

为了及时完成夫人的任务,skytim希望能够走一条最短的经过伞店的路径回家。但遗憾的是有些建筑正在施工,和它连着的道路都不能走。好在skytim早就对自己国家的地图烂熟于心,请你协助他找到最短的路径。

输入格式

输入第一行有两个整数n,m,表示地图的规模。

接下来m行每行n个数字,表示该建筑的状态。其中:

  • 0表示普通建筑,可以经过。
  • 1表示正在施工中的建筑,不可以经过。
  • 2表示skytim阅兵的位置。
  • 3表示skytim和Cyning的家。
  • 4表示伞店。

输出格式

输出一个整数,表示skytim最少需要移动的距离。

样例输入

8 4
4 1 0 0 0 0 1 0
0 0 0 1 0 1 0 0
0 2 1 1 3 0 4 0
0 0 0 4 1 1 1 0

样例输出

11

数据范围

对于 40 % 40\% 40% 的数据, 1 ≤ n , m ≤ 10 1 \le n,m \le 10 1n,m10​;

对于 100 % 100\% 100% ​​的数据, 1 ≤ n , m ≤ 1000 1 \le n,m \le 1000 1n,m1000​​​;

关于本题

        首先不得不说,这个题目是学习搜索很好的一个例题,题目的背景是地图问题,但是OJ上作业组也有很多数组地图系列的题目,方法都是一样的,所需要的知识包括一下内容:

  • 数组(这个不会不用看了,回去看课本去)
  • 函数的递归(程序设计知识)(深度优先搜索)
  • 队的简单操作(数据结构知识)(广度优先搜索)

        想要往下看的可以先对找上面的检查一下,其中第三点可以现学(现学就是可以,不要问我是怎么知道的,问就是我就是通过这个题目学会的queue的!)

关于搜索

        搜索: 搜素这里介绍两种方法:广度优先搜索【BFS】深度优先搜索【DFS】,顾名思义,广度优先搜索就是优先的覆盖面广的搜索,深度优先搜索就是优先搜索层次深的方法。如果是说地图上所有的地方都要搜索到的话,那么两个方法的时间复杂度是相同的。(我们假设搜索一个点的时间都是一样的,那么搜索区域大小一样,时间也一样)

广度优先搜索

        广度优先搜索就像病毒一样,慢慢的向周围扩散,或者说是辐射,emmm,举个例字,下面这个图就是一张 5 × 5 5\times 5 5×5 的地图,我们还是把这个表看做一个数组,左上角的坐标是 ( 0 , 0 ) (0,0) 0,0,第一行中间的坐标记为 ( 0 , 3 ) (0,3) 0,3
        我们假设起点坐标是 ( 2 , 2 ) (2,2) 2,2,就是浅蓝色的0处,我们假设一次移动可以且仅可以有下面四种操作:整个地图没有障碍物!全部可以通行

  • 向上一格
  • 向下一格
  • 向左一格
  • 向右一格
43234
32123
21012
32123
43234

       那么我们可以得出,第一次走可以行进到黄色的区域
       第二次走 最远 可以行进到黄色的区域。当然你也可以退回起点,这里我们只关注第 i i i 次行走可以走到的最远的地方,把这些地方都进行标记(即赋值为对应的步数 i i i )那么显然,我们想要得知某个位置从出发点最少需要走多少距离,直接访问这个地方的标记 i i i ,就可以得知到这个地方最少的步数!
       第三次走 最远 可以行进到橙色的区域。
       第四次走 最远 可以行进到绿色的区域。
       ……
       以上这个过程我们就模拟了广度优先搜索的过程!
       显然,搜索的过程要注意地图的边界,不能越界,数组的下标要自己保证合法性。

深度优先搜索

       深度优先搜素就是利用函数的递归,一根筋往下走,遇到障碍或者到达终点,就回溯。(课本上结束回溯的时候说道,如果不行就返回到上一步,其实我个人觉得这个说法有点不合适,实际上就是没有通过 if 判断语句,所以什么也不做,等待这个 void 函数执行完毕,自动退出,就回溯到啦上一个步骤)
       这里继续补充一下我对递归函数的思考,递归函数就是自己给自己挖一个洞,我调用我自己。【例如,下面的大圆圈表示第一次调用函数的函数入口,函数运行后又调用了自己,相当于大圆圈里面还有一个小圆圈,同理小圆圈继续调用自身这个函数,就形成了一个无底洞】。
       显然这是不科学的!为了避免无底洞现象,必须设置终止的条件!!!
请添加图片描述
       所以,写递归函数按照下面的语法来!可以避免无底洞现象。

void function(参数)
{
	// 写清楚终止条件
	if(终止条件)
		return;
	else
	// 根据实际情况情况调用自身这个函数
		function(参数);
}

       这里再补充一下对递归函数的思考,也是一个认识广度优先搜索和深度优先搜索的区别:我们来看看下面这个函数:

void function(初始参数)
{
	if(终止条件)
		return;
	else
		function(参数1);	// 第一个递归函数
		function(参数2);	// 第二个递归函数
		function(参数3);	// 第三个递归函数
		
}

       这个函数再运行的时候是怎么样的呢?显然,这一个函数可以导向三个自身的递归函数,那么导向这三个函数是同时的吗?显然不是,调用的时候先执行第一个语句:function(参数1); 然后进入到第二层函数,在第二层函数里面检查是否可以终止,不能终止就再一次的调用function(参数1); ,然后进入到第三层函数 ⋯ ⋯ \cdots \cdots ,一直执行到第 n n n 层函数,终止条件满足,return;语句执行,【补:一旦执行 return; 语句,函数直接终止, return; 后面的语句都不会执行】然后退回到第 n − 1 n-1 n1 层函数,执行function(参数2); // 第二个递归函数 ,然后再次递归,以此类推。
       所以上面这个过程,本质就是深度优先搜索,不管远近,一头撞到底,(执行到函数终止)然后回溯到上一层函数,开始下一个函数的递归语句。

队、类的基本知识

       队,顾名思义,把它理解成一个队伍就可以。【实际上是一种特殊的线性表】
       第一,使用队的时候要包含头文件#include <queue>
       第二,了解如何定义一个队,语法如下:

	queue<队伍元素的类型> 队名字

       其中队伍元素的类型可以是intdouble,或者是其他自定义的类型,为了便于后面的理解,我们先自定义一个类。【如果不懂类,可以把这个理解为一种或者多种类型集合,intdouble都是一个类,还不懂可以继续看】
       比如,我们要定义坐标这个类型,显然这个类型是c++之前我们没有接触过的,属于自定义的类,坐标这个类包括横坐标,纵坐标。横坐标数据类型是int, 纵坐标也是 int。类的定义方法如下

class 类型名 
{
	类型1	类型1的代表名
	类型2	类型2的代表名
	······
};

       然后如果我们要定义坐标这个类,可以这样:node是节点,也是这个类型的名字,如果你不知道这个名字该怎么用,想想int 你是怎么在用的你一定就明白了。

class node
{
	int x;
	int y;
};

       例如你要定义一个整型变量 a

int a;

       同理,定义一个坐标点,在有了上面class的声明以后,我们可以定义一个坐标,下面这个代码就表示定义了两个个名字分别叫deparuredestination的点

node deparure,destination;

       然鹅deparure里面包括横坐标纵坐标,所以我们可以通过下面的方法访问 deparure.x,deparure.y.表示里面的一个分支,下面举个例子赋值。

	deparure.x = 1;
	deparure.y = 2;

       说了这么多类的知识,我们还是回到队里面来,hhh队,例如我们定义一个名字叫做 bfsqueue 的队伍,队伍里元素都是 node 类型(我们刚刚定义过)

	queue<node> bfsqueue;
push 函数

       用途:在队尾插入一个元素
       函数传入参数:要插入的那个元素
       函数返回值:无
       举例:把 departure 这个元素放在队尾

	bfsqueue.push(departure);	// 括号里面的这个元素进入队尾
pop 函数

       用途:踢出队伍最前面的元素。
       函数传入参数:无。
       函数返回值:无
       举例:踢出bfsqueue 队伍里面最靠前的元素

	bfsqueue.pop();	// 队伍最前面的元素出队!
size 函数

       用途:统计元素个数
       函数传入参数:无。
       函数返回值:unsigned int 一个正整数。
       举例:统计bfsqueue 队伍里面元素个数,并输出:

	cout << bfsqueue.size();	// 队伍最前面的元素出队!
empty 函数

       用途:判断队伍里面是否元素个数为0。
       函数传入参数:无。
       函数返回值:true 或者 false
       举例:判断bfsqueue 队伍是否为空,非空则执行语句A;

	if(!bfsqueue.empty())
		A;
front 函数

       用途:返回队列中第一个元素,但是并没有剔除。
       函数传入参数:无。
       函数返回值:队列中第一个元素
       举例:把队列中第一个元素赋值给我们之前定义的 destination 这个变量。

	destination = bfsqueue.front();
back 函数

       用途:返回队列中最后一个元素,但是并没有剔除。
       函数传入参数:无。
       函数返回值:队列中最后一个元素
       举例:把队列中最后一个元素赋值给我们之前定义的 departure 这个变量。

	departure = bfsqueue.back();

memset 函数使用

  • 使用前要记得加上头文件<cstring>
  • 可以用来初始化 bool 数组全部为 true 或者 false
  • 不要用来初始化 int double 数组,否则会有惊喜,一定不是你想要的结果(初始化为 0 可以)
  • 使用方法如下
memset(数组名, 初始化值, sizeof(数组名));

题目解答

       首先这个题目是个模板题,没有学过的可以利用这个机会学会广度优先搜索,之前也自己脑补过写代码写了几百行都模拟不出来,所以,还是直接学习吧!先上AC代码!

// 所有坐标x代表行数,y代表列数!
// 本题坐标采用0-base!

#include <iostream>
#include <queue>
#include <cstring>
#include <cmath>
using namespace std;

class shop
{
    public:
        int x;
        int y;
}sh[1000000] = { 0,0 };

class node
{
    public:
        int x;
        int y;
};

int testmap[1010][1010];
int step[1010][1010];
// 定义增量x,y 数组,
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
// 定义shop数组

int shopnum = 0;
int m, n;

bool bfs_CheckNextstep(node checkobject)
{
    if (checkobject.x < 0 || checkobject.x >= m)
        return false;
    if (checkobject.y < 0 || checkobject.y >= n)
        return false;
    if (testmap[checkobject.x][checkobject.y] == 1)
        return false;
    if (step[checkobject.x][checkobject.y] != 0)
        return false;
    else
        return true;
    // if (nxt.x >= 0 && nxt.x < m && nxt.y >= 0 && nxt.y < n && step[nxt.x][nxt.y] == 0 && testmap[nxt.x][nxt.y] != 1)
    //     return true;
    // else
    //     return false;
}

// 广度优先搜索,传入的时候传入一个节点node!
void bfs_Main(node departure)
{
    memset(step, 0, sizeof(step));
    // 首先定义一个队列!
    queue<node> bfsque;
    bfsque.push(departure);
    node currentplace, nextplace;
    while (!bfsque.empty())
    {
        currentplace = bfsque.front();
        bfsque.pop(); // 把最前面的元素弹出去!
        for (int i = 0; i < 4; i++)
        {
            nextplace.x = currentplace.x + dx[i];
            nextplace.y = currentplace.y + dy[i];
            if (bfs_CheckNextstep(nextplace) == true)
            {
                step[nextplace.x][nextplace.y] = step[currentplace.x][currentplace.y] + 1;
                bfsque.push(nextplace);
            }
        }
    }
}

int main()
{
    cin >> n >> m;
    node departure, destination;
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            cin >> testmap[i][j];
            if (testmap[i][j] == 2)     
            {
                departure.x = i;
                departure.y = j;
            }
            if (testmap[i][j] == 3)     
            {
                destination.x = i;
                destination.y = j;
            }
            if (testmap[i][j] == 4)     
            {
                sh[shopnum].x = i;
                sh[shopnum].y = j;
                shopnum++;
            }
        }
    }

    int a[10000] = {0};
    int b[10000] = {0};
    bfs_Main(departure);
    for (int i = 0; i < shopnum; i++)
    {
        a[i] = step[sh[i].x][sh[i].y];
    }

    bfs_Main(destination);
    for (int i = 0; i < shopnum; i++)
    {
        b[i] = step[sh[i].x][sh[i].y];
    }

    long int ans = 1e8;
    long int temp;
    for (int i = 0; i < shopnum; ++i) 
    {
        if (a[i] > 0 && b[i] > 0)
        {
            temp = a[i] + b[i];
            ans = min(ans, temp);
        }  
    }
    cout << ans;
    system("pause");
    return 0;
}

       首先解释一下需要定义的变量,类型,见下:

class shop
{
    public:
       int x;	int y;
}sh[1000000] = { 0,0 };		// 定义一个商店类,类里面包括横纵坐标
class node
{
    public:
        int x;	int y;
};							// 定义一个坐标类

int testmap[1010][1010];	// 输入数据使用,记录输入的数组
int step[1010][1010];		// 统计地图上的点可以到达的最小步数
// 定义增量x,y 数组,要保证 dx[i],dy[i]当i从0递增到3的时候周围四个方向可以都过一遍
int dx[4] = {1, -1, 0, 0};	// 核心基础 增量数组
int dy[4] = {0, 0, 1, -1};	// 核心基础 增量数组
int shopnum = 0;			// 统计商店的个数
int m, n;					// m n 按照题目要求,地图规模

       然后可以看看广度优先搜素的详细讲解:广度优先搜索讲解的传送门 是人看得懂的!!!
       为防止链接失效,我还是搬运一下,不想走传送门直接往下看

        深度优先搜索方法可用于解决从迷宫起点开始寻找一条通往迷宫中目标位置最短路径的问题。广度优先搜索 / 宽度优先搜索 (BreadthFirst Search,BFS) 也可以解决这个问题。

        使用二维数组来存储这个迷宫。最开始的时候在迷宫 (1, 1) 处,可以往右走或者往下走。深度优先搜索的方法是先往右边走,然后一直尝试下去,直到走不通的时候再回到这里。深度优先可以通过函数的递归实现。广度优先搜索 / 宽度优先搜索 (Breadth First Search,BFS) 方法通过一层一层扩展的方法来寻找目标位置。扩展时每发现一个点就将这个点加入到队列中,直至走到目标位置 (ph, qw) 时为止。

        最开始在入口 (1, 1) 处,一步之内可以到达的点有 (1, 2)(2, 1)
请添加图片描述

        但是目标位置并不在这两个点上,只能通过 (1, 2)(2, 1) 这两点继续往下走。比如现在走到了 (1, 2) 这个点,之后又能够到达 (2, 2)。再看看通过 (2, 1) 又可以到达 (2, 2)(3, 1)。此时会发现 (2, 2) 这个点既可以从 (1, 2) 到达,也可以从 (2, 1) 到达,并且都只使用了 2 步。为了防止一个点多次被走到,这里需要一个数组来记录一个点是否已经被走到过。
请添加图片描述

        此时 2 步可以走到的点就全部走到了,有 (2, 2)(3, 1),可是目标位置并不在这两个点上。看来没有别的办法,还得继续往下尝试,看看通过 (2, 2)(3, 1) 这两个点还能到达哪些新的没有走到过的点。通过 (2, 2) 这个点我们可以到达 (2, 3)(3, 2),通过 (3, 1) 可以到达 (3, 2)(4, 1)。现在 3 步可以到达的点有 (2, 3)(3, 2)(4, 1),依旧没有到达目标的所在点,所以我们需要继续重复刚才的做法,直到找到目标位置所在点为止。
请添加图片描述
        回顾一下刚才的算法,可以用一个队列来模拟这个过程。在这里我们用一个结构体数组来实现队列。

struct note {
	int xw; // 横坐标
	int yh; // 纵坐标
	int step; // 步数 
};

struct note que[2500]; // 因为地图大小不超过 50*50,因此队列扩展不会超过 2500
int head, tail;
int map[51][51] = { 0 }; // 用来存储地图 
int book[51][51] = { 0 }; // 数组 book 的作用是记录哪些点已经在队列中了,防止点被重复扩展,并全部初始化为 0

/* 最开始的时候需要进行队列初始化,即将队列设置为空 */
head = 0;
tail = 0;

// 第一步将 (1, 1) 加入队列,并标记 (1, 1) 已经走过。
que[tail].xw = 1;
que[tail].yh = 1;
que[tail].step = 0;
tail++;

book[1][1] = 1;

在这里插入图片描述

        然后从 (1, 1) 开始,先尝试往右走到达了 (1, 2)

txw = que[head].xw + 1;
tyh = que[head].yh;
12

        需要判断 (1, 2) 是否越界。

if ((txw < 1) || (txw > W) || (tyh < 1) || (tyh > H))
{
	continue;
}
1234

        再判断 (1, 2) 是否为障碍物或者已经在路径中。

if ((0 == map[tyh][txw]) && (0 == book[tx][ty]))
{
}
123

        如果满足上面的条件,则将 (1, 2) 入队,并标记该点已经走过。

book[tyh][txw] = 1; // bfs 每个点通常情况下只入队一次,同深搜不同,不需要将 book 数组还原
// 插入新的点到队列中
que[tail].xw = txw;
que[tail].yh = tyh;
que[tail].step = que[head].step + 1; // 步数是父亲的步数 + 1
tail++;
123456

在这里插入图片描述

        接下来还要继续尝试往其他方向走。在这里我们规定一个顺序,即按照顺时针的方向来尝试 (右 -> 下 -> 左 -> 上)。我们发现从 (1, 1) 还是可以到达 (2, 1),因此也需要将 (2, 1) 也加入队列,代码实现与刚才对 (1, 2) 的操作是一样的。
在这里插入图片描述

        对 (1, 1) 扩展完毕后,此时我们将 (1, 1) 出队 (因为扩展完毕,已经没用了)。

head++;
1

        接下来我们需要在刚才新扩展出的 (1, 2)(2, 1) 这两个点上继续向下探索 (因为还没有到达目标所在的位置,所以还需要继续)。(1, 1) 出队之后,现在队列的 head 正好指向了 (1, 2) 这个点,现在我们需要通过这个点继续扩展,通过 (1, 2) 可以到达 (2, 2),并将 (2, 2) 也加入队列。
在这里插入图片描述
        (1, 2) 这个点已经处理完毕,于是可以将 (1, 2) 出队。(1, 2) 出队之后,head 指向了 (2, 1) 这个点。通过 (2, 1) 可以到达 (2, 2)(3, 1),但是因为 (2, 2) 已经在队列中,因此我们只需要将 (3, 1) 入队。
在这里插入图片描述
        到目前为止我们已经扩展出从起点出发 2 步以内可以到达的所有点,可是依旧没有到达目标所在的位置,因此还需要继续扩展,直到找到目标所在的点才算结束。
        为了方便向四个方向扩展,这里需要一个 next 数组:

	// right, down, left, up - (height, width)
	int next[4][2] = { { 0, 1 },{ 1, 0 },{ 0, -1 },{ -1, 0 } };

        到这里已经全部讲解完了,bfs你也应该会了。我们来看看这个题广度优先搜索的核心代码:

// 广度优先搜索,传入的时候传入一个节点node!
void bfs_Main(node departure)
{
    memset(step, 0, sizeof(step));	// step数组初始化全部为0
    queue<node> bfsque;				// 首先定义一个队列!
    bfsque.push(departure);			// 起点入队
    node currentplace, nextplace;	// 定义两个节点,当前位置和下一个位置
    while (!bfsque.empty())			// 当队列不是空的时候执行循环操作
    {
        currentplace = bfsque.front();			// 读取队伍的最开始的元素
        bfsque.pop(); 							// 把最前面的元素弹出去!(踢掉)
        for (int i = 0; i < 4; i++)				// 向四个方向查找
        {
            nextplace.x = currentplace.x + dx[i];	// 计算下一步可能的x坐标
            nextplace.y = currentplace.y + dy[i];	// 计算下一步可能的y坐标
            if (bfs_CheckNextstep(nextplace) == true)	// 检测下一步是否合法
            {
                										// 下一步合法,走!
                step[nextplace.x][nextplace.y] = step[currentplace.x][currentplace.y] + 1;
               	// 更新step数组,为下一点到达所需要的最少步数
                bfsque.push(nextplace);
                // 下一个节点入队!
            }
        }
    }
}

        关于检测下一步是否合法的函数(单独拎出来纯属为了好看)

bool bfs_CheckNextstep(node checkobject)
{
    if (checkobject.x < 0 || checkobject.x >= m)
        return false;
    if (checkobject.y < 0 || checkobject.y >= n)		// 数组越界 拒绝!
        return false;
    if (testmap[checkobject.x][checkobject.y] == 1)		// 地图不能走,拒绝!
        return false;
    if (step[checkobject.x][checkobject.y] != 0)		// 之前已经走过,拒绝!
        return false;
    else												// 满足条件,接受!
        return true;
    // if (nxt.x >= 0 && nxt.x < m && nxt.y >= 0 && nxt.y < n && step[nxt.x][nxt.y] == 0 && testmap[nxt.x][nxt.y] != 1)
    //     return true;
    // else
    //     return false;								// 这段代码也可以,就是长了点
}

        最后,我要说,这题最坑的地方是!!!!看下图请添加图片描述
       这个是我没有想到的!居然会有这种商店!!!红色的是不能走的,蓝色是商店,按照我们题目中的算法,没有走到的,不可达到的地方在step数组里面都是 0 !!所以emmm 要检查一下!

       最后回顾一下解题思想,整体就是,首先要把所有的商店都检测一遍,放在数组里面,然后从起点搜索一遍所有商店到达起点的距离,从终点搜索一遍所有商店到达终点的距离,对应的商店加起来就可以!(但是!!但是!!但是!!一定遇到上面那种周围被包围的商店,距离是多少?是0!所以求和之前要检查一下距离是不是 0 ,0代表距离无穷远!)
       所以,最后看一看 main 函数

int main()
{
    cin >> n >> m;
    node departure, destination;
    for (int i = 0; i < m; i++)
    {
        for (int j = 0; j < n; j++)
        {
            cin >> testmap[i][j];
            if (testmap[i][j] == 2)     
            {
                departure.x = i;
                departure.y = j;		// 起点
            }
            if (testmap[i][j] == 3)     
            {
                destination.x = i;
                destination.y = j;		// 终点
            }
            if (testmap[i][j] == 4)     
            {
                sh[shopnum].x = i;		
                sh[shopnum].y = j;		// 统计商店
                shopnum++;
            }
        }
    }

    int a[10000] = {0};
    int b[10000] = {0};
    bfs_Main(departure);
    for (int i = 0; i < shopnum; i++)
    {
        a[i] = step[sh[i].x][sh[i].y];
    }

    bfs_Main(destination);
    for (int i = 0; i < shopnum; i++)
    {
        b[i] = step[sh[i].x][sh[i].y];
    }

    long int ans = 1e8;
    long int temp;
    for (int i = 0; i < shopnum; ++i) 
    {
        if (a[i] > 0 && b[i] > 0)			// 一定要这一步!!不能省略!!!防止无法到达的商店!
        {
            temp = a[i] + b[i];
            ans = min(ans, temp);
        }  
    }
    cout << ans;
    system("pause");
    return 0;
}

       平安结束!

评测编号用户题目名称评测状态运行时间内存语言提交时间
49617               1028. 采购Accepted233ms30888KiBC++Jul-22-2021 20:37:07
49616                1028. 采购Accepted320ms30884KiBC++Jul-22-2021 20:17:05
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值