【算法】王晓东期末考题总结(一)


分治

实现思路可参考:【算法】分治算法
之前写的Java版有思路。

  1. 二分搜索
#include <iostream>
#include <vector>

using namespace std;

// 二分搜索函数
int binarySearch(const vector<int>& array, int target) {
    int left = 0;
    int right = array.size() - 1;

    while (left <= right) {
        int mid = left + (right - left) / 2;

        // 如果找到目标元素,返回其索引
        if (array[mid] == target) {
            return mid;
        }

        // 如果目标元素在数组的左半部分,缩小搜索范围到左半部分
        if (array[mid] > target) {
            right = mid - 1;
        }
        // 如果目标元素在数组的右半部分,缩小搜索范围到右半部分
        else {
            left = mid + 1;
        }
    }

    // 如果数组中没有找到目标元素,返回-1表示未找到
    return -1;
}

int main() {
    vector<int> array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int target = 6;

    // 调用二分搜索函数
    int result = binarySearch(array, target);

    // 输出结果
    if (result != -1) {
        cout << "元素 " << target << " 在数组中的索引为 " << result << endl;
    } else {
        cout << "未找到元素 " << target << endl;
    }

    return 0;
}
  1. 合并排序
#include <iostream>
#include <vector>

using namespace std;

// 合并两个有序数组
void merge(vector<int>& array, int left, int mid, int right) {
    int n1 = mid - left + 1;
    int n2 = right - mid;

    // 创建临时数组存储左右两个子数组
    vector<int> leftArray(n1);
    vector<int> rightArray(n2);

    // 复制数据到临时数组 leftArray 和 rightArray
    for (int i = 0; i < n1; i++) {
        leftArray[i] = array[left + i];
    }
    for (int j = 0; j < n2; j++) {
        rightArray[j] = array[mid + 1 + j];
    }

    // 合并左右子数组
    int i = 0; // 初始化左子数组的索引
    int j = 0; // 初始化右子数组的索引
    int k = left; // 初始化合并后的数组的索引

    while (i < n1 && j < n2) {
        if (leftArray[i] <= rightArray[j]) {
            array[k] = leftArray[i];
            i++;
        } else {
            array[k] = rightArray[j];
            j++;
        }
        k++;
    }

    // 将左子数组的剩余部分复制到合并后的数组
    while (i < n1) {
        array[k] = leftArray[i];
        i++;
        k++;
    }

    // 将右子数组的剩余部分复制到合并后的数组
    while (j < n2) {
        array[k] = rightArray[j];
        j++;
        k++;
    }
}

// 归并排序
void mergeSort(vector<int>& array, int left, int right) {
    if (left < right) {
        // 找到数组的中间位置
        int mid = left + (right - left) / 2;

        // 递归地对左右两部分进行排序
        mergeSort(array, left, mid);
        mergeSort(array, mid + 1, right);

        // 合并已排序的两部分
        merge(array, left, mid, right);
    }
}

int main() {
    vector<int> array = {12, 11, 13, 5, 6, 7};

    cout << "原始数组: ";
    for (int num : array) {
        cout << num << " ";
    }
    cout << endl;

    // 调用归并排序函数
    mergeSort(array, 0, array.size() - 1);

    cout << "排序后的数组: ";
    for (int num : array) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}
  1. 快速排序
#include <iostream>
#include <vector>

using namespace std;

// 交换数组中两个元素的位置
void swap(vector<int>& array, int i, int j) {
    int temp = array[i];
    array[i] = array[j];
    array[j] = temp;
}

// 根据选定的基准元素将数组划分为两部分,并返回基准元素的索引
int partition(vector<int>& array, int low, int high) {
    // 选择最右侧元素作为基准
    int pivot = array[high];
    int i = low - 1;  // i是小于等于基准元素的子数组的最后一个元素的索引

    // 遍历数组,将小于等于基准元素的元素放在左侧,大于基准元素的元素放在右侧
    for (int j = low; j < high; j++) {
        if (array[j] <= pivot) {
            i++;
            swap(array, i, j);
        }
    }

    // 将基准元素放在正确的位置
    swap(array, i + 1, high);

    return i + 1;
}

// 快速排序的递归函数
void quickSort(vector<int>& array, int low, int high) {
    if (low < high) {
        // 划分数组,获取基准元素的索引
        int pivotIndex = partition(array, low, high);

        // 递归排序左侧子数组
        quickSort(array, low, pivotIndex - 1);
        // 递归排序右侧子数组
        quickSort(array, pivotIndex + 1, high);
    }
}


int main() {
    vector<int> array = {12, 4, 5, 6, 7, 3, 1, 15};

    cout << "Original array: ";
    for (int num : array) {
        cout << num << " ";
    }

    // 调用快速排序算法
    quickSort(array, 0, array.size() -1);

    cout << "\nSorted array: ";
    for (int num : array) {
        cout << num << " ";
    }

    return 0;
}
  1. 最接近点对问题

最接近点对问题是一个经典的计算几何问题,目标是找到平面上一组点中距离最近的两个点。分治算法是解决这个问题的一种有效方法。下面是最接近点对问题的分治算法实现思路:

  • 按照 x 坐标将点集排序
    将点集按照 x 坐标的顺序进行排序。这可以通过常见的排序算法(如快速排序)实现。
  • 分治:
    将排序后的点集平均分成两部分,分别递归求解左右两部分的最接近点对。
  • 查找跨越中间的最接近点对:
    在左右两部分的边界附近,找到距离中间线最近的两个点。这一步的时间复杂度是线性的。
  • 合并:
    比较左右两部分的最接近点对,选择其中的最小值作为候选。
  • 找到最终的最接近点对:
    在之前的步骤中,我们找到了两个候选点对,分别来自左右两部分。最终的最接近点对要么在其中一个部分,要么跨越两个部分。
  • 计算最终的最接近点对:
    在考虑跨越边界的情况时,需要检查距离边界线距离小于当前最小距离的点。如果找到更小的距离,更新最小距离。
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <limits>

using namespace std;

struct Point {
    double x, y;
};

// 按照 x 坐标升序排序
bool compareX(const Point& a, const Point& b) {
    return a.x < b.x;
}

// 按照 y 坐标升序排序
bool compareY(const Point& a, const Point& b) {
    return a.y < b.y;
}

// 计算两点之间的距离
double distance(const Point& a, const Point& b) {
    return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2));
}

// 暴力解法,计算所有点对之间的距离,找到最小值
pair<Point, Point> bruteForceClosestPair(const vector<Point>& points, int left, int right) {
    double minDist = numeric_limits<double>::max();
    pair<Point, Point> closestPair;

    for (int i = left; i <= right; ++i) {
        for (int j = i + 1; j <= right; ++j) {
            double dist = distance(points[i], points[j]);
            if (dist < minDist) {
                minDist = dist;
                closestPair = {points[i], points[j]};
            }
        }
    }

    return closestPair;
}

// 在给定的点集中找到最接近的点对
pair<Point, Point> closestPairUtil(const vector<Point>& points, int left, int right) {
    if (right - left <= 3) {
        // 对于小规模问题,使用暴力解法
        return bruteForceClosestPair(points, left, right);
    }

    int mid = (left + right) / 2;

    // 分别递归求解左右两边的最近点对
    pair<Point, Point> leftClosest = closestPairUtil(points, left, mid);
    pair<Point, Point> rightClosest = closestPairUtil(points, mid + 1, right);

    // 取左右两边的最近点对中的最小距离
    double minDist = min(distance(leftClosest.first, leftClosest.second),
                         distance(rightClosest.first, rightClosest.second));

    // 在距离中间线小于minDist的点集中查找可能更小的距离
    vector<Point> strip;
    for (int i = left; i <= right; ++i) {
        if (abs(points[i].x - points[mid].x) < minDist) {
            strip.push_back(points[i]);
        }
    }

    // 按照 y 坐标升序排序 strip
    sort(strip.begin(), strip.end(), compareY);

    // 在 strip 中查找可能更小的距离
    for (int i = 0; i < strip.size(); ++i) {
        for (int j = i + 1; j < strip.size() && (strip[j].y - strip[i].y) < minDist; ++j) {
            double dist = distance(strip[i], strip[j]);
            if (dist < minDist) {
                minDist = dist;
                leftClosest = {strip[i], strip[j]};
            }
        }
    }

    // 返回最终的最近点对
    return leftClosest;
}

// 最接近点对算法的入口函数
pair<Point, Point> closestPair(const vector<Point>& points) {
    // 按照 x 坐标升序排序点集
    vector<Point> sortedPoints = points;
    sort(sortedPoints.begin(), sortedPoints.end(), compareX);

    return closestPairUtil(sortedPoints, 0, sortedPoints.size() - 1);
}

int main() {
    vector<Point> points = {{0, 0}, {1, 1}, {2, 2}, {3, 3}, {5, 5}, {7, 7}, {8, 8}};

    // 调用最接近点对算法
    pair<Point, Point> closestPairPoints = closestPair(points);

    // 输出结果
    cout << "最接近点对: (" << closestPairPoints.first.x << ", " << closestPairPoints.first.y << ") and ("
         << closestPairPoints.second.x << ", " << closestPairPoints.second.y << ")" << endl;

    return 0;
}

动态规划

实现思路可参考:【算法】动态规划(Dynamic Programming)

  1. 矩阵连乘
#include<iostream>
using namespace std;
 
#define N 7  //N为7,实际表示有6个矩阵
/*
*矩阵链构造函数:构造m[][]和s[][]
*m中存储的值是计算出来的最小乘法次数,比如m[1][5]就是A1A2A3A4A5的最小乘法次数
*s中存储的是获取最小乘法次数时的断链点,s[1][5]对应的就是如何拆分A1A2A3A4A5,
*比如S[1][5]=3可表示:(A1A2A3)(A4A5),当然内部断链还会继续划分A1A2A3
*/
int MatrixChain(int *p, int n, int m[][N], int s[][N]){
    for(int i=1;i<=n;i++){     //矩阵链中只有一个矩阵时,次数为0,注意m[0][X]时未使用的
        m[i][i]=0;
    }
    for(int r=2;r <= n;r++){    //矩阵链长度,从长度为2开始
        for(int i=1;i <= n-r+1;i++){    //根据链长度,控制链最大的可起始点
            int j = i+(r-1);  //矩阵链的末尾矩阵,注意r-1,因为矩阵链为2时,实际是往右+1
            m[i][j] = m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j];   //先设置最好的划分方法就是直接右边开刀,后续改正,也可合并到下面的for循环中
            s[i][j]=i;
            for(int k=i+1;k < j;k++){   //这里面将断链点从i+1开始,可以断链的点直到j-1为止
                int t = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
                if(t<m[i][j]){
                    m[i][j] = t;
                    s[i][j] = k;
                }
 
            }
 
        }
    }
    return m[1][n];
}
/*
*追踪函数:根据输入的i,j限定需要获取的矩阵链的始末位置,s存储断链点
*/
void Traceback(int i,int j, int s[][N]){
    if(i==j)       //回归条件
    {
        cout<<"A"<<i;
    }
    else       //按照最佳断点一分为二,接着继续递归
    {
        cout<<"(";
        Traceback(i,s[i][j],s);
        Traceback(s[i][j]+1,j,s);
        cout<<")";
    }
}
int main(){
	int p[N]={30,35,15,5,10,20,25};
	int m[N][N],s[N][N];
 
	int minCost = MatrixChain(p,N-1,m,s);//N-1因为只有六个矩阵
    Traceback(1,6,s);
    cout << "最小计算代价为: " << minCost << endl;
	return 0;
}
  1. 流水作业调度

流水作业调度问题(Flow Shop Scheduling Problem)是一个经典的调度问题,通常涉及到多个作业(jobs)需要在多台机器上执行。每个作业都有一系列的任务(tasks),并且每个任务需要在不同的机器上执行。流水作业调度的目标是找到一个调度方案,使得完成所有作业的时间最短。

参考:法设计与分析】流水作业调度问题 动态规划算法与代码实现 超详细

#include<iostream>
#include<algorithm>
#include<math.h>
#define NUM 5		//工件总数
#define POWNUM 32	//2^NUM 状态总数

using namespace std;

//2 5 7 3 6 2 4 7 6 9 8 2
//答案:35

//2 5 4 2 3 3 6 1 1 7
//答案:19

class JiHe
{
public:
	int a[NUM];			//1:被加工 0:不被加工
	int num;			//当前状态下 被加工的工件个数
	int minTime[100];	//数组下标是等待时间t
};

class GongJian
{
public:
	int t1;				//该工件在 M1 上加工需要的时间
	int t2;				//该工件在 M2 上加工需要的时间
};

GongJian gongjian[NUM];
JiHe jihe[POWNUM];

//寻找最小时间的递归函数
int findMinTime(JiHe jihe, int t)
{
	if (jihe.num == 0)			//集合中无元素时 等待时间就是加工时间
	{
		return t;
	}

	int i;
	int curMinTime;
	int mintime = 1000;			//初始化巨大值
	JiHe withoutI = jihe;

	for (i = 0; i < NUM; i++)	//i放在第一个加工 循环找到最小值情况下的i 但是i没有被记录
	{
		withoutI = jihe;
		if (jihe.a[i] == 1)
		{
			withoutI.a[i] = 0;
			withoutI.num = jihe.num - 1;
			curMinTime = gongjian[i].t1 + findMinTime(withoutI, gongjian[i].t2 + max(t - gongjian[i].t1, 0));
			if (curMinTime < mintime)
			{
				mintime = curMinTime;
			}
		}
	}
	return mintime;
}


int main()
{
	//每个工件的时间状况
	cout << "请输入工件在机器M1 M2上的加工时间:\n";
	for (int i = 0; i < NUM; i++)
	{
		cout << "工件序号" << i << "的加工时间\n";
		cout << "t1 = ";
		cin >> gongjian[i].t1;
		cout << "t2 = ";
		cin >> gongjian[i].t2;
	}



	//填写每个工件在与不在的状态 32个
	int i;
	int x[NUM];
	for (int i = 0; i < NUM; i++)//用于二进制计数
	{
		x[i] = 1;
	}
	int num;
	int t, p;
	for (i = 0; i < POWNUM; i++)
	{
		//二进制计数 罗列所有工件组合
		for (t = NUM - 1, p = 0; t >= 0; t--, p++)
		{
			if ((i % (int)pow(2, t)) == 0)
			{
				x[p] = -x[p];
			}
		}
		/*if (i % 32 == 0)x[0] = -x[0];
		if (i % 16 == 0)x[1] = -x[1];
		if (i % 8 == 0)x[2] = -x[2];
		if (i % 4 == 0)x[3] = -x[3];
		if (i % 2 == 0)x[4] = -x[4];
		if (i % 1 == 0)x[5] = -x[5];*/

		for (t = 0; t < NUM; t++)//把-1改成0
		{
			jihe[i].a[t] = (x[t] > 0 ? x[t] : 0);
		}
		/*jihe[i].a[0] = (x[0] > 0 ? x[0] : 0);
		jihe[i].a[1] = (x[1] > 0 ? x[1] : 0);
		jihe[i].a[2] = (x[2] > 0 ? x[2] : 0);
		jihe[i].a[3] = (x[3] > 0 ? x[3] : 0);
		jihe[i].a[4] = (x[4] > 0 ? x[4] : 0);
		jihe[i].a[5] = (x[5] > 0 ? x[5] : 0);*/

		//填写当前状态下有几个工件被加工 num
		num = 0;
		for (int j = 0; j < NUM; j++)
		{
			if (jihe[i].a[j] == 1)num++;
		}
		jihe[i].num = num;
	}

	//自底向上 计算每个状态的最短时间
	int k;
	int workNum;
	for (workNum = 0; workNum <= NUM; workNum++)
	{
		// 0,1,2,...,workNum 个工件被加工
		for (i = 0; i < POWNUM; i++)
		{
			if (jihe[i].num == workNum)
			{
				//等待时间k
				for (k = 0; k < 100; k++)
				{
					jihe[i].minTime[k] = findMinTime(jihe[i], k);
				}
			}
		}
	}

	cout << "最短时间:" << jihe[POWNUM - 1].minTime[0] << endl;
	system("pause");
}
  1. 背包问题
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// 物品结构体
struct Item {
    int weight; // 物品重量
    int value;  // 物品价值
};

// 动态规划解决0/1背包问题
int knapsack(const vector<Item>& items, int capacity) {
    int numItems = items.size();

    // 创建二维数组存储最大价值
    vector<vector<int>> dp(numItems + 1, vector<int>(capacity + 1, 0));

    // 填充动态规划表
    for (int i = 1; i <= numItems; i++) {
        for (int w = 1; w <= capacity; w++) {
            if (items[i - 1].weight <= w) {
                // 当前物品可以放入背包
                dp[i][w] = max(dp[i - 1][w], items[i - 1].value + dp[i - 1][w - items[i - 1].weight]);
            } else {
                // 当前物品不能放入背包
                dp[i][w] = dp[i - 1][w];
            }
        }
    }

    // 返回最终的最大价值
    return dp[numItems][capacity];
}

int main() {
    // 定义一组物品
    vector<Item> items = {{2, 3}, {3, 4}, {4, 5}, {5, 6}};

    // 背包容量
    int capacity = 8;

    // 调用0/1背包问题算法
    int maxValue = knapsack(items, capacity);

    // 输出结果
    cout << "背包中物品的最大总价值为: " << maxValue << endl;

    return 0;
}

贪心算法

实现思路可参考:【算法】贪心算法

  1. 最优装载问题
#include<iostream>
#include<algorithm>

using namespace std;

int main() {
    int n;//定义集装箱个数
    double c;//定义船的最大承载量
    cout << "输入船的最大承载量以及集装箱个数" << endl;
    cin >> c >> n;
    cout << "输入每件集装箱的重量,用空格分开" << endl;
   
     double * w =new double[n];//用数组填装集装箱的个数
    for (int i = 0; i < n; i++) {
        cin >> w[i];
    }
   //冒泡排序
    sort(w, w+n);

    double temp = 0.0;//中间值(相当于一个集装箱装进去,然后将集装箱装到船上)
    int count = 0;//计数器
    for (int i = 0; i < n; i++) {
        if (temp + w[i] <= c) {
            temp += w[i];
            count++;
        }
        else {
            break;
        }
    }

    cout << "最多可装下" << count << endl;
    return 0;
}
  1. 单源最短路径

贪心算法解决单源最短路径问题的经典算法是Dijkstra算法。Dijkstra算法通过贪心策略逐步确定从源节点到各个其他节点的最短路径,具体实现思路如下:
按各个顶点与源点之间路径长度的递增次序,生成源点到各个顶点的最短路径的方法,即先求出长度最短的一条路径,再参照它求出长度次短的一条路径,以此类推,直到源点到其他各个顶点的最短路径全部求出为止。(Dijkstra算法思想)

假定源点u,顶点集合V被划分为两部分,集合S和V-S,其中S中的顶点到源点的最短路径的长度已经确定,集合V-S中所包含的顶点到源点的最短路径的长度待定,称从源点出发,只进过S中的点到达V-S中的点的路径为特殊路径.

#include<iostream>
#include<algorithm>

using namespace std;
#define N 5
#define M 9999999 

void Dijkstra(int n, int v, int dist[], int p[], int c[][N+1])
{
	bool s[N+1];//顶点集合S
	for(int i = 1; i <= n; i++){
		dist[i] = c[v][i];//从源点到顶点i的最短特殊路径长度
		s[i] = false;
		
		if(dist[i] == M){
			p[i] = 0; // 从源点到顶点i的最短路径上千一个顶点 
		} 
		else
		{
			p[i] = v;
		}
	} 
	dist[v] = 0;
	s[v] = true;
	for(int i = 1; i < n; i++){
		int temp = M;
		int u = v;//上一顶点
		//找到具有最短特殊路径长度的顶点u
		for(int j = 1; j <= n; j++){
			if((!s[j]) && (dist[j] < temp))
			{
				u = j;
				temp = dist[j];
			}
		} 
		s[u] = true;
		//更新dist值
		for(int j = 1; j <= n; j++){
			if((!s[j]) && c[u][j] < M)
			{
				int newdist = dist[u] + c[u][j];
				if(newdist < dist[j]){
					dist[j] = newdist;
					p[j] = u;
				}
			}
		} 
	}
	
}
//输出最短路径v源点,i终点 
void Traceback(int v, int i, int p[])
{
	//源点等于终点,即找出全部路径
	if(v == i){
		cout << i;
		return;
	} 
	Traceback(v, p[i], p);
	cout << "->" << i; 
}

int main(){
	int v = 1;//源点 为1
	int dist[N+1];//从源点到顶点i的最短特殊路径长度 
	int p[N+1]; //从源点到顶点i的最短路径上其哪一个顶点
	//带权有向图的邻接矩阵,行和列从下标从1开始 
	int c[N+1][N+1];
	//输入:如果输入-1,代表两个点不是邻接点,初始化为M(代表无穷大) 
	cout << "带权有向图的邻接矩阵为:" << endl;
	for(int i = 1; i <= N; i++){
		for(int j = 1; j <= N; j++){
			cin >> c[i][j];
			if(c[i][j] == -1){
				c[i][j] = M;
			}
		}
	}
	//寻找最短路径 
	Dijkstra(N, v, dist, p, c);
	//输出
	cout << "源点1到源点5的最短路径长度为:" << dist[5] << endl; 
	cout << "路径为:" << endl;
	Traceback(1, 5, p);
	return 0;
}
  1. 最小生成树

贪心算法解决最小生成树问题的一个经典算法是Prim算法。Prim算法基于贪心策略,逐步构建最小生成树。以下是Prim算法的实现思路:

// 【贪心算法】最小生成树问题 - Prim算法
#include <iostream> 
using namespace std;

#define M 9999	// maxint,inf,大数
const int N = 6;// 无向连通带权图的 顶点数

void Prim(int n, int c[][N + 1])
{
	// 顶点j属于V-S,closest[j]是顶点j在S中的 最邻接顶点
	int closest[N + 1];	// j的 最邻接顶点
	// 顶点j属于V-S,顶点j到closest[j]和S中的其它邻接顶点k相比
	// c[j][closest[j]] <= c[j][k],即最小权值
	int lowcost[N + 1];	// 最小权值,c[j][closest[j]]

	bool s[N + 1];		// 顶点集合S

	s[1] = true;		// 初始S={1}

	//初始化
	for (int i = 2; i <= n; i++)
	{
		closest[i] = 1;
		lowcost[i] = c[1][i];
		s[i] = false;
	}

	for (int i = 1; i < n; i++)
	{
		int min = M;
		int j = 1;
		// 找出V-S中使权值最小的顶点j
		for (int k = 2; k <= n; k++)
		{
			if ((lowcost[k] < min) && (!s[k]))
			{
				min = lowcost[k];
				j = k;
			}
		}
		// 找到符合贪心选择方式的边,将顶点j加入到集合S
		cout << closest[j] << "--" << j << endl;
		s[j] = true;

		// 找到一条边后,更新数组closest和lowcost
		for (int k = 2; k <= n; k++)
		{
			if ((c[j][k] < lowcost[k] && (!s[k])))
			{
				lowcost[k] = c[j][k];
				closest[k] = j;
			}
		}
	}
}

int main()
{
	// 无向连通带权图的邻接矩阵,行和列下标从1开始
	int c[N + 1][N + 1] = {
		{M,	M,	M,	M,	M,	M,	M},
		{M,	M,	6,	1,	5,	M,	M},
		{M,	6,	M,	5,	M,	3,	M},
		{M,	1,	5,	M,	5,	6,	4},
		{M,	5,	M,	5,	M,	M,	2},
		{M,	M,	3,	6,	M,	M,	6},
		{M,	M,	M,	4,	2,	6,	M},
	};
	
	cout << "无向连通带权图的矩阵为:\n";
	for (int i = 1; i <= N; i++)
	{
		for (int j = 1; j <= N; j++)
			cout << c[i][j] << "\t";
		cout << endl;
	}

	cout << "\nPrim算法最小生成树选边次序:" << endl;
	Prim(N, c);
	
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值