深圳大学——算法设计与分析——金罐游戏



一、实验目的

(1) 掌握动态规划算法设计思想。

(2) 掌握金罐游戏问题的动态规划解法。

二、实验内容与要求

1.实验内容

金罐游戏中有两个玩家,A和B,所有的金罐排成一排,每个罐子里都有一些金币, 玩 家 可以看到每个金罐中有多少硬币。A和B两个玩家交替轮流打开金罐,但是必须从一排的 某一端开始挑选,玩家可以从一排罐的任一端挑选一个罐打开。 获胜者是最后拥有更多 硬币的玩家。 我们是A玩家,问如何才能使A 收集的硬币数量最大。
假设 B 也是按照“最佳”策略玩,并且 A 开始游戏。

2.实验要求

1、给出解决问题的动态规划方程;
2、随机产生金罐的个数和金币值,对小数据模型利用蛮力法测试算法的正确性,并记录A选择的是哪个金罐;
3、随机产生金罐的个数和金币值,对不同数据规模(n的值)测试算法效率,并与理论效率进行比对,请提供能处理的数据最大规模,注意要在有限时间内处理完;
4、该算法是否有效率提高的空间?包括空间效率和时间效率。

三、实验步骤与过程

(一)蛮力法

1.算法描述

以玩家A先手在[4,6,3,2]中选择为例
在这里插入图片描述
如果玩家A选择左边,那么玩家B后面有两种选择:左边或右边,因为玩家B也是最优策略,所以玩家B做出的选择要使得玩家A获得的金币最少;反之如果玩家A选择右边也相同,两个玩家都在以最优的策略使对方得到的金币数量最少,这是一个递归的过程。在这个例子中,玩家A作为先手玩家,最终选择了2和6,金币数量比玩家B多一个。
递归:
        ①定义一个maxCoins(l,r)递归函数,表示一个先手的玩家可以在[l,r]区间中的罐子里所取到的最大金币数量。
        ②确定递归函数的终止条件,当l=r时,说明只有一个罐子,那么先手的玩家能取得的最大金币数就是该罐子所装的金币数。当l+1=r时,说明有两个罐子,那么先手玩家能取到的最大金币数就是装的金币数最多的那个罐子的数量。
        ③对一般情况进行判断,也就是当罐子多于两个时,那么先手的玩家能取到最大金币数的可能情况有两种,一种是左边的罐子金币数量+后手玩家选择后先手玩家能在剩下的区间中得到金币的最小值(因为后手玩家也是以最优策略进行选择,所以后手玩家只会采取他选择后使你能得到的金币数量最少的策略)。

2.时间复杂度分析

因为在这个递归函数中,每次都考虑到了先手玩家和后手玩家选取左右两端的罐子的两种选择,因此,对于n个罐子,存在 2 n − 1 2^{n-1} 2n1个选择,所以蛮力法的时间复杂度为:
                                                                                     T ( n ) = O ( 2 n ) T(n)=O(2^n) T(n)=O(2n)

3.核心代码实现

//朴素递归模拟蛮力法:
int maxCoinsA_dg(int l,int r){
    if(l==r)
        return coins[l];
    if(r-l==1)
        return coins[l]>coins[r]?coins[l]:coins[r];
    if(r-l>=2){
        //如果玩家A选择左边,那么玩家B后面有两种选择:左边或右边
        //因为玩家B也是最优策略,所以玩家B做出的选择要使得玩家A获得的金币最少
        //反之如果玩家A选择右边也相同
        int ll=coins[l]+min(maxCoinsA_dg(l+1,r-1), maxCoinsA_dg(l+2,r));
        int rr=coins[r]+min(maxCoinsA_dg(l,r-2), maxCoinsA_dg(l+1,r-1));
        return ll>rr?ll:rr;
    }
}

4.效率分析

O ( 2 n ) O(2^n) O2n理论时间推导:
T 1 = 2 n 1 × t T_1 =2^{n_1}\times t T1=2n1×t          T 2 = 2 n 2 × t T_2 =2^{n_2}\times t T2=2n2×t          T 1 T 2 = 2 n 1 2 n 2 \frac{T_1}{T_2} =\frac{2^{n_1}}{2^{n_2}} T2T1=2n22n1
   
以数据量为20的数据运行时间为基准点,计算其他数据量的理论运行时间

数据量5101520253035
实际耗时0.000225ms0.00305ms0.09265ms1.78605ms87.6576ms1467.53ms78500.2ms
理论耗时0.000095ms0.002895ms0.055814ms1.78605ms57.1536ms2805.043ms46960.96ms

通过分析可知,对于实验中给出的数据量,时间复杂度大致满足于 O ( 2 n ) O(2^n) O2n,但可以看出实际运行的时间要比理论运行时间长,这是因为递归函数在运行的过程中会开辟栈空间,因此实际时间会稍长与理论时间,而且该影响会随着数据量的增加会越来越明显,因为蛮力法的时间复杂度是 O ( 2 n ) O(2^n) O2n,呈指数型增长。
        

(二)蛮力法可优化

1.算法描述

从(一)中蛮力法的实现方式可以看到,有些数值存在重复运算,因此,可以使用数组对已经运算出的结果进行保存,从而减少程序的运行时间。

2.核心代码

int dg_Memory[NUM][NUM];//拿个数组保存递归的记忆,防止重复计算耗时太长

int maxCoinsA_better_dg(int l,int r){
    if(l==r)
        return coins[l];
    if(r-l==1)
        return coins[l]>coins[r]?coins[l]:coins[r];
    if(dg_Memory[l][r]!=-1)
        return dg_Memory[l][r];
    int ll=coins[l]+min(maxCoinsA_better_dg(l+1,r-1), maxCoinsA_better_dg(l+2,r));
    int rr=coins[r]+min(maxCoinsA_better_dg(l,r-2), maxCoinsA_better_dg(l+1,r-1));
    dg_Memory[l][r]=ll>rr?ll:rr;
    return dg_Memory[l][r];
}

3.效率分析

由于优化的蛮力法采用数组来避免了重复计算,因此时间复杂度不再符合 O ( 2 n ) O(2^n) O2n

数据量10100100010000
实际耗时0.0015ms0.0853ms9.6026ms964.077ms

对比两个蛮力法的实验数据,可以很明显的看出,通过使用数组记录已经计算过的数值优化后的蛮力法,效率得到了巨大的提升,说明未经优化的蛮力法中出现了许多重复计算的情况。
        

(三)动态规划

1.算法描述

        ①定义一个二维数组 d p [ i ] [ j ] dp[i][j] dp[i][j],该数组中元素的意义是在区间 [ i , j ] [i,j] [i,j]的罐子中先手玩家与后手玩家最终得到的金币数量的差值。
        ②如果玩家A取了 [ i , j ] [i,j] [i,j]中左端的罐子coins[i],那么玩家B在 [ i + 1 , j ] [i+1,j] [i+1,j]就作为先手玩家,玩家B与玩家A最终得到金币的差值为 d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j],那么玩家A在 [ i , j ] [i,j] [i,j]中与玩家B最终得到金币的差值 d p [ i ] [ j ] = c o i n s [ i ] − d p [ i + 1 ] [ j ] dp[i][j]=coins[i]-dp[i+1][j] dp[i][j]=coins[i]dp[i+1][j],反之如果玩家A取了 [ i , j ] [i,j] [i,j]中右端的罐子 c o i n s [ j ] coins[j] coins[j],那么玩家B在 [ i , j − 1 ] [i,j-1] [i,j1]就作为先手玩家,玩家B与玩家A最终得到金币的差值为 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1],那么玩家A在 [ i , j ] [i,j] [i,j]中与玩家B最终得到金币的差值 d p [ i ] [ j ] = c o i n s [ j ] − d p [ i ] [ j − 1 ] dp[i][j]=coins[j]-dp[i][j-1] dp[i][j]=coins[j]dp[i][j1]。因为玩家A采取的是最优策略,那么可推出动态规划方程为:
        
                                         d p [ i ] [ j ] = m a x ( c o i n s [ i ] − d p [ i + 1 ] [ j ] , c o i n s [ j ] − d p [ i ] [ j − 1 ] ) dp[i][j]=max(coins[i]-dp[i+1][j],coins[j]-dp[i][j-1]) dp[i][j]=max(coins[i]dp[i+1][j],coins[j]dp[i][j1])
        
        ③最终得到 d p [ 0 ] [ n − 1 ] dp[0][n-1] dp[0][n1]动态的值就是玩家A与玩家B最终得到金币的差值,因为 d p [ 0 ] [ n − 1 ] dp[0][n-1] dp[0][n1]表示的是在区间 [ 0 , n − 1 ] [0,n-1] [0,n1]中先手玩家与后手玩家最终得到的金币数量的差值,而玩家A正是在这个区间中作为先手玩家。
         ④为了记录玩家A取罐子的序号,定义一个数组 i n d e x [ i ] [ j ] index[i][j] index[i][j] i n d e x [ i ] [ j ] index[i][j] index[i][j]数组记录对应区间每次“先手”玩家选择左端或右端罐子的序号,最后以玩家A与玩家B的金币数量差值是否与蛮力法的相同来检验算法的正确性。如果蛮力法也想要记录玩家A取罐子的序号,也可参考此方法。
         ⑤注意使用dp数组时,要先给数组对角线初始化,即dp[i][i]=coins[i],表示的意义是只有一个罐子的情况下,那么先手玩家赢后手玩家的金币数就是这个罐子的金币数。

2.时间复杂度分析

在动态规划方法中,只需要计算出数组中 n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)个元素的值即可。因此对于动态规划方法求解金罐问题的时间复杂度应是:
                                                                         T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)

3.核心代码

int coins[NUM];//coins数组记录金罐中装的金币的数量
int dp[NUM][NUM];
int index[NUM][NUM];//dp数组用于动态规划,index数组用来查找玩家A,B选择的金罐
vector<int> A_choice;//保存玩家A选择的金罐
vector<int> B_choice;//保存玩家B选择的金罐
//二维数组动态规划
void maxCoinsA_dp(){
    for(int i=NUM-1;i>=0;i--){
        for(int j=i+1;j<NUM;j++){
            int l= coins[i] - dp[i + 1][j];
            int r= coins[j] - dp[i][j - 1];
            if(l>r){
                dp[i][j]=l;
                index[i][j]=i;
            }
            else{
                dp[i][j]=r;
                index[i][j]=j;
            }
        }
    }
}

以上是二维数组动态规划并用index数组来记录玩家AB的选择,那么怎么最后怎么读取出来个玩家的选择已经两个玩家的最终金币数量呢?如下:

        int sum=0;//统计金币总数
        for(int i=0;i<NUM;i++)
            sum+=coins[i];

        //找出玩家A选择的罐子
        for(int l=0,r=NUM-1;l<=r;){
            //玩家A根据最优策略选择金罐
            A_choice.push_back(index[l][r]);
            if(index[l][r]==l)
                l++;
            else
                r--;

            //玩家B根据最优策略选择金罐
            B_choice.push_back(index[l][r]);
            if(index[l][r]==l)
                l++;
            else
                r--;
        }

//两个变量分别记录玩家A和玩家B的金币数量
        int coins_A=0;
        int coins_B;

        //找出玩家A和玩家B的金币总数
        for(int i=0;i<=A_choice.size()-1;i++){
            coins_A+=coins[A_choice[i]];
        }
        cout<<endl;
        coins_B=sum-coins_A;
        cout<<"玩家A:"<<coins_A<<"coins   玩家B:"<<coins_B<<"coins"<<endl;

        //来个华丽的分割线
        cout<<"------------------------"<<endl;
//输出通过index数组找出的玩家A选择的金罐
    cout<<"玩家A选择的金罐子序号分别是:";
    for(int i:A_choice){
        cout<<" "<<i;
    }

4.效率分析

O ( n 2 ) O(n^2) On2理论时间推导:
T 1 =   n 1 2 × t T_1 ={\ n}_1^2\times t T1= n12×t           T 2 =   n 2 2 × t T_2 ={\ n}_2^2\times t T2= n22×t          T 1 T 2 = n 1 2 n 2 2 \frac{T_1}{T_2} =\frac{n_1^2}{n_2^2} T2T1=n22n12
 
以数据量为9000的数据运行时间为基准点,计算其他数据量的理论运行时间

数据量3000600090001200015000
实际耗时/ms25.5916113.565216.398443.522597.482
理论耗时/ms28.3912596.17689216.398384.7076693.0031

通过分析可知,对于实验中给出的数据量,时间复杂度大致满足于O(n2),但可以看出实际运行的时间与理论运行时间依然存在误差,这可能是因为数据的偶然性导致的,以及计算机在运行过程当中存在不可避免的误差,误差在可接受范围之内。

(四)降低空间复杂度的动态规划

1.算法描述

通过分析(三)中的动态规划方程可以看出, d p [ i ] [ j ] dp[i][j] dp[i][j]的值只和 d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j] d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1]有关,当得到 d p [ i ] [ j ] dp[i][j] dp[i][j]的值时, d p [ i + 1 ] [ j ] dp[i+1][j] dp[i+1][j]的值后续不会再使用,而 d p [ i ] [ j − 1 ] dp[i][j-1] dp[i][j1]的值后续还会用到,那么可以省略i这个维度,使用一维数组 d p [ j ] dp[j] dp[j]来代替二维数组 d p [ i ] [ j ] dp[i][j] dp[i][j],对对空间进行优化。其动态规划方程为:
                         d p [ j ] = m a x ( c o i n s [ i ] − d p [ j ] , c o i n s [ j ] − d p [ j − 1 ] ) dp[j]=max(coins[i]-dp[j],coins[j]-dp[j-1]) dp[j]=max(coins[i]dp[j],coins[j]dp[j1])

2.时间复杂度分析

虽然节省了空间,但是还是要计算 n ( n + 1 ) 2 \frac{n(n+1)}{2} 2n(n+1)个元素的值,因此时间复杂度为:
                                                                                   T ( n ) = O ( n 2 ) T(n)=O(n^2) T(n)=O(n2)

3.核心代码

int coins[NUM];//coins数组记录金罐中装的金币的数量
int dp[NUM];
int index[NUM][NUM];//dp数组用于动态规划,index数组用来查找玩家A,B选择的金罐


vector<int> A_choice;//保存玩家A选择的金罐
vector<int> B_choice;//保存玩家B选择的金罐

//一维数组动态规划
void maxCoinsA_better_dp(){
    for(int i=NUM-1;i>=0;i--){
        for(int j=i+1;j<NUM;j++){
            int l= coins[i] - dp[j];
            int r= coins[j] - dp[j-1];
            if(l>r){
                dp[j]=l;
                index[i][j]=i;
            }
            else{
                dp[j]=r;
                index[i][j]=j;
            }
        }
    }
}

3.效率分析

O ( n 2 ) O(n^2) On2理论时间推导:
T 1 =   n 1 2 × t T_1 ={\ n}_1^2\times t T1= n12×t           T 2 =   n 2 2 × t T_2 ={\ n}_2^2\times t T2= n22×t          T 1 T 2 = n 1 2 n 2 2 \frac{T_1}{T_2} =\frac{n_1^2}{n_2^2} T2T1=n22n12
 
以数据量为9000的数据运行时间为基准点,计算其他数据量的理论运行时间

数据量3000600090001200015000(max)3000000
实际耗时/ms21.895591.6566205.443354.499552.56823593600
理论耗时/ms22.9141591.308205.443365.232553.904722827000

可以看出,优化后的动态规划方法实际耗时与理论耗时非常拟合,这可能是因为减少了开辟的空间,所以减少了访问空间的时间,使得实际耗时与理论耗时更加接近。

(五)四个方法效率对比

对比四个方法的实验数据,可以看出普通蛮力法的效率是非常低的,计算35个金罐的时间都远远超过后三个方法计算10000个金罐的时间,优化后的蛮力法效率得到大幅提升,但依旧不及动态规划方法的效率,同时降低空间复杂度的动态规划方法实际效率也略快于普通的动态规划方法,因此在金罐游戏这个实验当中,毫无疑问,求解此问题动态规划方法是我们的首要选择。

四、心得体会

1.动态规划方法的步骤:①将大问题转化成小问题,②写出动态规划方程,③使用数组来表示相应问题中的含义,对数组进行填充,④查看是否还有优化的可能,降低空间复杂度,化二维数组为一维数组。
 
2.算法不能单单考虑时间复杂度,同时应该也要考虑空间复杂度,算法的空间消耗很重要。
 
 
 

本人也是深大20级的,提供源码仅供参考,请不要抄袭哈!
在这里插入图片描述

四个方法汇总代码

#include <iostream>
#include<bits/stdc++.h>
#include <windows.h>
using namespace std;

#define NUM 30 //金罐的数量

int coins[NUM];//coins数组记录金罐中装的金币的数量
int DP[NUM];
int dp[NUM][NUM],index[NUM][NUM];//dp数组用于动态规划,index数组用来查找玩家A,B选择的金罐
//其中在dp数组中,dp[i][j]的意义是在区间[i,j]的罐子中,先手的玩家与后手的玩家得到的金币差
int dg_Memory[NUM][NUM];//拿个数组保存递归的记忆,防止重复计算耗时太长

vector<int> A_choice;//保存玩家A选择的金罐
vector<int> B_choice;//保存玩家B选择的金罐

//动态规划
void maxCoinsA_dp(){
    for(int i=NUM-1;i>=0;i--){
        for(int j=i+1;j<NUM;j++){
            int l= coins[i] - dp[i + 1][j];
            int r= coins[j] - dp[i][j - 1];
            if(l>r){
                dp[i][j]=l;
                index[i][j]=i;
            }
            else{
                dp[i][j]=r;
                index[i][j]=j;
            }
        }
    }
}

//减小空间复杂度的动态规划
void maxCoinsA_better_dp(){
    for(int i=NUM-1;i>=0;i--){
        for(int j=i+1;j<NUM;j++){
            int l= coins[i] - DP[j];
            int r= coins[j] - DP[j-1];
            if(l>r){
                DP[j]=l;
            }
            else{
                DP[j]=r;
            }
        }
    }
}

//蛮力法:
int maxCoinsA_dg(int l,int r){
    if(l==r)
        return coins[l];
    if(r-l==1)
        return coins[l]>coins[r]?coins[l]:coins[r];
    if(r-l>=2){
        //如果玩家A选择左边,那么玩家B后面有两种选择:左边或右边
        //因为玩家B也是最优策略,所以玩家B做出的选择要使得玩家A获得的金币最少
        int ll=coins[l]+min(maxCoinsA_dg(l+1,r-1), maxCoinsA_dg(l+2,r));
        int rr=coins[r]+min(maxCoinsA_dg(l,r-2), maxCoinsA_dg(l+1,r-1));
        return ll>rr?ll:rr;
    }
}

//优化后的蛮力法
int maxCoinsA_better_dg(int l,int r){
    if(l==r)
        return coins[l];
    if(r-l==1)
        return coins[l]>coins[r]?coins[l]:coins[r];
    if(dg_Memory[l][r]!=-1)
        return dg_Memory[l][r];
    int ll=coins[l]+min(maxCoinsA_better_dg(l+1,r-1), maxCoinsA_better_dg(l+2,r));
    int rr=coins[r]+min(maxCoinsA_better_dg(l,r-2), maxCoinsA_better_dg(l+1,r-1));
    dg_Memory[l][r]=ll>rr?ll:rr;
    return dg_Memory[l][r];
}

int main() {
    cout<<"金罐数量:"<<NUM<<endl;
    srand(time(0));
    for(int i=0;i<NUM;i++){
        coins[i]=rand()%10;
    }
    double time_dp=0.0;//记录动态规划耗时
    double time_better_dp=0.0;//记录减小空间复杂度的动态规划耗时
    double time_dg=0.0;//记录递归耗时
    double time_better_dg=0.0;//记录优化后的递归耗时
    //动态规划:
    //初始化
    for(int i=0;i<NUM;i++){
        for(int j=0;j<NUM;j++){
            dg_Memory[i][j]=-1;
            dp[i][j]=-1;
            index[i][j]=-1;
        }
    }

    for(int i=0;i<NUM;i++){
        DP[i]=coins[i];
        dp[i][i]=coins[i];
        index[i][i]=i;
    }
    //动态规划
    LARGE_INTEGER Freq;
    LARGE_INTEGER BeginTime_dp;
    LARGE_INTEGER EndTime_dp;

    QueryPerformanceFrequency(&Freq);
    QueryPerformanceCounter(&BeginTime_dp);
    maxCoinsA_dp();
    QueryPerformanceCounter(&EndTime_dp);
    time_dp = (double)(EndTime_dp.QuadPart - BeginTime_dp.QuadPart)/(double)Freq.QuadPart;
    cout<<"玩家A和玩家B的金币差为:"<<dp[0][NUM-1]<<endl;
    cout<<"动态规划求解耗时:"<<time_dp*1000<<"ms"<<endl;

    //来个华丽的分割线
    cout<<"------------------------"<<endl;

    //减小空间复杂度的动态规划
    QueryPerformanceFrequency(&Freq);
    QueryPerformanceCounter(&BeginTime_dp);
    maxCoinsA_better_dp();
    QueryPerformanceCounter(&EndTime_dp);
    time_better_dp = (double)(EndTime_dp.QuadPart - BeginTime_dp.QuadPart)/(double)Freq.QuadPart;
    cout<<"玩家A和玩家B的金币差为:"<<DP[NUM-1]<<endl;
    cout<<"优化的动态规划求解耗时:"<<time_better_dp*1000<<"ms"<<endl;

    //来个华丽的分割线
    cout<<"------------------------"<<endl;

    int sum=0;//统计金币总数
    for(int i=0;i<NUM;i++)
        sum+=coins[i];

    //蛮力法:
    LARGE_INTEGER Freq_dg;
    LARGE_INTEGER BeginTime_dg;
    LARGE_INTEGER EndTime_dg;
    QueryPerformanceFrequency(&Freq_dg);
    QueryPerformanceCounter(&BeginTime_dg);
    int coins_A= maxCoinsA_dg(0,NUM-1);
    QueryPerformanceCounter(&EndTime_dg);
    time_dg = (double)(EndTime_dg.QuadPart - BeginTime_dg.QuadPart)/(double)Freq_dg.QuadPart;
    int coins_B=sum-coins_A;
    cout<<"玩家A:"<<coins_A<<"coins   玩家B:"<<coins_B<<"coins"<<endl;
    cout<<"玩家A和玩家B的金币差为:"<<coins_A-coins_B<<endl;
    cout<<"蛮力法求解耗时:"<<time_dg*1000<<"ms"<<endl;

    //来个华丽的分割线
    cout<<"------------------------"<<endl;

    //优化后的蛮力法:
    QueryPerformanceFrequency(&Freq_dg);
    QueryPerformanceCounter(&BeginTime_dg);
    int coins_AA= maxCoinsA_better_dg(0,NUM-1);
    QueryPerformanceCounter(&EndTime_dg);
    time_better_dg=(double)(EndTime_dg.QuadPart - BeginTime_dg.QuadPart)/(double)Freq_dg.QuadPart;
    int coins_BB=sum-coins_AA;
    cout<<"玩家A:"<<coins_AA<<"coins   玩家B:"<<coins_BB<<"coins"<<endl;
    cout<<"玩家A和玩家B的金币差为:"<<coins_AA-coins_BB<<endl;
    cout<<"优化后的蛮力法求解耗时:"<<time_better_dg*1000<<"ms"<<endl;


    //找出玩家A选择的罐子
    for(int l=0,r=NUM-1;l<=r;){
        //玩家A根据最优策略选择金罐
        A_choice.push_back(index[l][r]);
        if(index[l][r]==l)
            l++;
        else
            r--;

        //玩家B根据最优策略选择金罐
        B_choice.push_back(index[l][r]);
        if(index[l][r]==l)
            l++;
        else
            r--;
    }

    //来个华丽的分割线
    cout<<"------------------------"<<endl;

    //输出通过index数组找出的玩家A选择的金罐
    cout<<"玩家A选择的金罐子序号分别是:";
    for(int i=0;i<=A_choice.size()-1;i++){
        cout<<" "<<A_choice[i];
    }
    cout<<endl;


}```

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值