学会背包问题

背包问题的经典解法:动态规划

总结,背包问题

  • 状态转移时,关注此时装还是不装当前位置的物品
  • 如果可以一个物品 i 可以无限取用,关注 i 所在行
  • 如果一个物品 i 只能取用一次,关注 i - 1 的上一行

一、背包问题I

找最大价值,每个物品只能放一次

1、题目描述
背包问题I

在 n 个物品中挑选若干物品装入背包,最多能装多满?
假设背包的大小为m,每个物品的大小为Ai(每个物品只能选择一次且物品大小均为正整数),A数组中是各个物品的大小

数组A[3, 4, 5, 8],m = 12,输出:9,解释:装 4、5
数组A[2, 3, 5, 7],m = 12,输出:12,解释:装 5、7

2、解题丝路

状态:

  • F[ i ][ j ] 表示 [ 0, i ] 中挑选若干物品,装到容量为 j 的背包中,能达到的最大收纳量(最大价值)(j 的范围是 [ 0, m])

状态转移方程:

  • 如果不装 A[ i ],此时的最大收纳量 就是 j 容量的背包装 [ 0, i - 1] 物品的最大收纳量,F[ i ][ j ] = F[ i - 1][ j ]
  • 如果 A[ i ] > j,说明根本不可能装 i ,F[ i ][ j ] = F[ i - 1][ j ];
  • 如果 A [ i ] <= j,说明可以装 i ;如果不装 i ,最大收纳量就是F[ i - 1][ j ];如果装 i ,就要腾出 A[ i ] 容量,最大收纳量 = 当前容量 j 去除 A[ i ] 容量,剩余容量装 [ 0, i - 1] 物品的最大收纳量 + A[ i ],即 F[ i - 1][ j - A[i] ] + A[ i ];综上,F[ i ][ j ] = max( F[ i - 1][ j ], F[ i - 1][ j - A[i] ] + A[ i ])

初始值:

  • 针对第一个物品(i = 0),如果容量 j >= 此物品的容量A[ i ],可放入,F[ 0 ][ j ] = A[ 0 ]

最终解:

  • 返回 F[ A.length - 1 ][ m ]
    在这里插入图片描述

3、代码炸裂

    public int backPack(int m, int[] a) {
        int n = a.length;
        int[][] array = new int[n][m + 1];

        for (int i = 0; i <= m; i++) {
            if(i >= a[0]){
                array[0][i] = a[0];
            }
        }

        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= m; j++) {
                if(a[i] > j){
                    array[i][j] = array[i - 1][j];
                } else {
                    array[i][j] = Math.max(array[i - 1][j], array[i - 1][j - a[i]] + a[i]);
                }
            }
        }
        return array[n - 1][m];
    }

4、空间优化

F[ i ][ j ]层的值,只与 F[ i - 1][ k ] (k <= j)的值有关,可以使用一维数组,注意 j 要从后向前遍历

	public int backPack(int m, int[] a) {
        int n = a.length;
        int[] array = new int[m + 1];

        for (int i = 0; i <= m; i++) {
            if(i >= a[0]){
                array[i] = a[0];
            }
        }

        for (int i = 1; i < n; i++) {
            // 注意:此处 j 必须逆序遍历
            for (int j = m; j >= a[i]; j--) {
                array[j] = Math.max(array[j], array[j - a[i]] + a[i]);
            }
        }

        return array[m];
    }

5、更换初始值

多设置一行,其体积为 0,这一行就作为初始值, F[ 0 ][ j ] = 0
在这里插入图片描述

	public int backPack(int m, int[] a) {
        int n = a.length;
        int[] array = new int[m + 1];
        
        for (int i = 1; i <= n; i++) {
            // 注意:此处 j 必须逆序遍历
            for (int j = m; j >= a[i - 1]; j--) {
                array[j] = Math.max(array[j], array[j - a[i - 1]] + a[i - 1]);
            }
        }
        
        return array[m];
    }

背包II
背包问题II,赋予物品价值,求最大价值,与背包I相同

	public int backPackII(int m, int[] a, int[] v) {
        int n = a.length;
        int[][] array = new int[n][m + 1];

        for (int i = 0; i <= m; i++) {
            if(i >= a[0]){
                array[0][i] = v[0];
            }
        }

        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= m; j++) {
                if(a[i] > j){
                    array[i][j] = array[i - 1][j];
                } else {
                    array[i][j] = Math.max(array[i - 1][j], array[i - 1][j - a[i]] + v[i]);
                }
            }
        }
        return array[n - 1][m];
    }

三、背包问题III

找最大价值,每个物品可以无限放

1、题目描述
背包问题III

给定 n 种物品, 每种物品都有无限个. 第 i 个物品的体积为 A[i], 价值为 V[i].
再给定一个容量为 m 的背包. 问可以装入背包的最大价值是多少?

A = [2, 3, 5, 7]
V = [1, 5, 2, 4]
m = 10
输出: 15
解释: 装入3、3、3, 总价值 15.

2、解题丝路

状态:

  • F[ i ][ j ] 表示 [ 0, i ] 中挑选若干物品(同一个物品可以多次装入),装到容量为 j 的背包中,能达到的最大价值(j 的范围是 [ 0, m])

状态转移方程:
关于 F[ i ][ j ]

  • A[ i ] > j,说明根本不可能装 i,F[ i ][ j ] = F[ i - 1 ][ j ]
  • A[ i ] <= j,说明可以装 i,
  • 背包中不装 i (一个 i 也没有),最大价值 = F[ i - 1 ][ j ]
  • 此时再装一个 i (可能已经有 i ,也可能没有 i),最大价值 = F[ i - 1][ j - A[i] ] + V[ i ])
  • F[ i ][ j ] = max(F[ i - 1 ][ j ],F[ i - 1][ j - A[i] ] + V[ i ]))

初始值:

  • 针对第一个物品(i = 0),计算最多的个数,再 * V[ 0 ]

最终解:

  • 返回 F[ A.length - 1 ][ m ]

在这里插入图片描述

3、代码哗啦

    public int backPackIII(int[] a, int[] v, int m) {
        int n = a.length;
        int[][] array = new int[n][m + 1];

        for (int i = 0; i <= m; i++) {
            int count = i / a[0];
            array[0][i] = count * v[0];
        }

        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= m; j++) {
                if(a[i] > j){
                    array[i][j] = array[i - 1][j];
                } else {
                    array[i][j] = Math.max(array[i - 1][j], array[i][j - a[i]] + v[i]);
                }
            }
        }
        return array[n - 1][m];
    }

4、空间优化

空间优化,F[ i ][ j ] 只和 F[ i ][ k ](k < j )、F[ i - 1][ j ]有关,可以通过一维数组,进行空间优化,j 从前向后遍历
同时可以更换初始值,多设置一行,其体积为 0,价值为 0,这一行就作为初始值, F[ 0 ][ j ] = 0

     public int backPackIII(int[] a, int[] v, int m) {
        int n = a.length;
        int[] array = new int[m + 1];
        
        for (int i = 1; i <= n; i++) {
            for (int j = a[i - 1]; j <= m; j++) {
                array[j] = Math.max(array[j], array[j - a[i - 1]] + v[i - 1]);
            }
        }
        return array[m];
    }

四、背包问题IV

两种情况

  • 放满,每个物品可以无限放
  • 放满,每个物品可以只能放一次

1、题目描述
背包问题IV(无限放)

给出 n 个物品, 以及一个数组, nums[i]代表第i个物品的大小, 保证大小均为正数并且没有重复, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品可以使用无数次

输入: nums = [2,3,6,7] 和 target = 7
输出: 2
解释: 方案有: [7] [2, 2, 3]

背包问题V(只能放一次)

给出 n 个物品, 以及一个数组, nums[i] 代表第i个物品的大小, 保证大小均为正数, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品只能使用一次

A [1,2,3,3,7], m = 7
返回 2
结果的集合为:[7][1,3,3]

2、解题丝路
将目光聚焦到容量上

状态:

  • F[ i ][ j ] 表示 [0,i] 物品中选取若干物品(无线放或只能放一次),可以放满 j 容量的方案数

状态转移方程

  • 关于 F[ i ][ j ],可以使用 [ 0,i - 1 ]物品装满 j 容量,即可以将F[ i ][ j ] 赋初值, F[ i ][ j ] = F[ i - 1 ][ j ](这些方案中都不包括 i )
  • A[ i ] > j,说明不可能装 i,只能使用 [ 0,i - 1 ]物品装满 j 容量, F[ i ][ j ]不变
  • A[ i ] = j,说明可以只用一个 i 装满 j 容量,F[ i ][ j ] ++;
  • A[ i ] > j,说明可以装 i:
  • 如果可以重复选,F[ i ][ j - A[ i ] ]的每种方案中添加一个 i,即可装满 j 容量,F[ i ][ j ] += F[ i ][ j - A[ i ] ]
    如果是不可以重复选,那么就只能是在F[ i - 1 ][ j - A[ i ] ]的每种方案中添加一个 i, 即可装满 j 容量,F[ i ][ j ] += F[ i - 1 ][ j - A[ i ] ]

初始值

  • 可重复选时,针对第一个物品(i = 0),是 A[ i ] 倍数的容量 j 才能放满,F[ 0 ][ j ] = 1
  • 不可重复选时,针对第一个物品(i = 0),只有与 A[ i ] 相等的容量 j 才能放满,F[ 0 ][ j ] = 1

返回结果

  • F[ A.length - 1 ][ m ]

在这里插入图片描述

在这里插入图片描述

3、代码结局

  • 每个物品可以无限放
	public int backPackIV(int[] nums, int target) {
        int n = nums.length;
        int[][] array = new int[n][target + 1];

        for (int i = 0; i <= target; i++) {
            if(nums[0] <= i && i % nums[0] == 0){
                array[0][i] = 1;
            }
        }

        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= target; j++) {
                array[i][j] = array[i - 1][j];
                if(nums[i] == j){
                    array[i][j]++;
                } else if(nums[i] < j){
                    array[i][j] += array[i][j - nums[i]];
                }
            }
        }

        return array[n - 1][target];
    }

初始值设置,多设置一行,其体积为 0,F[ 0 ][ 0 ] = 1, (0 填满 0 只 1 种方案)
空间优化,F[ i ][ j ] 只和 F[ i ][ k ](k < j )、F[ i - 1][ j ]有关,可以通过一维数组,进行空间优化,j 从前向后遍历

    public static int backPackIV3(int[] nums, int target) {
        int n = nums.length;
        int[] array = new int[target + 1];
        
        array[0] = 1;
        
        for (int i = 1; i <= n; i++) {
            for (int j = nums[i - 1]; j <= target; j++) {
                 if(nums[i - 1] <= j){
                    array[j] += array[j - nums[i - 1]];
                }
            }
        }

        return array[target];
    }
  • 每个物品可以只能放一次
    public int backPackV(int[] nums, int target) {
        int n = nums.length;
        int[][] array = new int[n][target + 1];

        for (int i = 0; i <= target; i++) {
            if(nums[0] == i){
                array[0][i] = 1;
                break;
            }
        }

        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= target; j++) {
                array[i][j] = array[i - 1][j];
                if(nums[i] == j){
                    array[i][j]++;
                }
                if(nums[i] < j){
                    array[i][j] += array[i - 1][j - nums[i]];
                }
            }
        }

        return array[n - 1][target];
    }
    public int backPackV2(int[] nums, int target) {
        int n = nums.length;
        int[] array = new int[target + 1];

        array[0] = 1;

        for (int i = 1; i <= n; i++) {
            for (int j = target; j >= 0; j--) {
                if(nums[i - 1] <= j){
                    array[j] += array[j - nums[i - 1]];
                }
            }
        }
        return array[target];
    }

五、相似题目

1、分割等和子集

分割等和子集
与背包问题V相似
通过总和取半确定背包容量,nums就是物品的容量,判断是否可以放满背包(每个物品只能放一次)

    public boolean canPartition(int[] nums) {
        int val = 0;
        for (int num : nums) {
            val += num;
        }
        
        // 总和是奇数无法分割
        if(val % 2 == 1){
            return false;
        }
        
        // 要达到的目标值,即背包的容量;数组 nums 就是各个物品的重量
        int target = val / 2;
        
        boolean[] array = new boolean[target + 1];
        for (int i = 0; i <= target; i++) {
            if(nums[0] == i){
                array[i] = true;
            }
        }

        for (int i = 1; i < nums.length; i++) {
            for (int j = target; j >= 0; j--) {
                if(nums[i] <= j){
                    array[j] = array[j] || array[j - nums[i]];
                }
            }
        }
        
        return array[target];
    }
2、一和零

每个物品放一次,找最大数目,物品体积、背包容积要求多样化

1、题目描述
一和零

给你一个二进制字符串数组 strs 和两个整数 m 和 n 。
请你找出并返回 strs 的最大子集的长度,该子集中 最多 有 m 个 0 和 n 个 1 。
如果 x 的所有元素也是 y 的元素,集合 x 是集合 y 的 子集 。

输入:strs = [“10”, “0001”, “111001”, “1”, “0”], m = 5, n = 3
输出:4
解释:最多有5 个 0 和 3 个 1 的最大子集是 {“10”,“0001”,“1”,“0”} ,因此答案是 4 。 其他满足题意但较小的子集包括{“0001”,“1”} 和 {“10”,“1”,“0”} 。{“111001”} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3。

与背包问题I相似
每个字符串中0的数目、1的数目就类似于物品的体积,允许子集中最多m个0、n个1就类似于背包的容量,最多可以放物品的数量(每个物品只能放一次)

2、解题丝路

定义状态:

  • F[ i ][ j ][ k ]表示背包容量为 j 个0,k 个1,从[ 0,i ]物品中选取,可放入物品的最多数目

状态转移:

  • 对于 i 字符串,考虑当前容量放不放 i
  • 如果 i 中 0 的个数(zero) > j,或 1 的个数(one) > k,没法放入 i,F[ i ][ j ][ k ] = F[ i - 1 ][ j ][ k ]
  • 否则,可以放入 i,先腾地方,F[ i ][ j ][ k ] = max(F[ i - 1 ][ j ][ k ],F[ i - 1 ][ j - zero ][ k - one ] + 1 );

初始状态:

  • 可以多设置一行,为空字符串,F[ 0 ][ j ][ k ] = 0

最终结果:

  • F[ len ][ m ][ n ]

3、代码解释

  • 三维数组
    public int findMaxForm(String[] strs, int m, int n) {
        int len = strs.length;
        int[][][] array = new int[len + 1][m + 1][n + 1];

        for (int i = 1; i <= len; i++) {
            // count[0] 表示 str[i - 1] 中 0 的数量;count[1] 表示 str[i - 1]中 1 的数量
            int[] count = countZeroAndOne(strs[i - 1]);
            for (int j = 0; j <= m; j++) {
                for (int k = 0; k <= n; k++) {
                    array[i][j][k] = array[i - 1][j][k];
                    if(count[0] <= j && count[1] <= k){
                        array[i][j][k] = Math.max(array[i - 1][j][k], array[i - 1][j - count[0]][k - count[1]] + 1);
                    }
                }
            }
        }
        return array[len][m][n];
    }
    // 计算字符串中 0 和 1 的数量
    private int[] countZeroAndOne(String s) {
        int[] count = new int[2];
        for (char ch : s.toCharArray()) {
            count[ch - '0']++;
        }
        return count;
    }
  • 二维数组,背包容量就是[ 0,m ]与[ 0,n ]的组合
    在这里插入图片描述
public static int findMaxFormII(String[] strs, int m, int n) {
        int len = strs.length;
        int[] ones = new int[len];     // 记录每个字符串中 1 的个数
        int[] zeros = new int[len];     // 记录每个字符串中 0 的个数

        for (int i = 0; i < len; i++) {
            String s = strs[i];
            for (char ch : s.toCharArray()) {
                if(ch == '1'){
                    ones[i]++;
                } else {
                    zeros[i]++;
                }
            }
        }

        int col = (m + 1) * (n + 1);

        int[][] array = new int[len][col];
        for (int i = 0; i < col; i++) {
            int sz = i/(n + 1);
            int so = i%(n + 1);
            if( sz >= zeros[0] && so >= ones[0]){
                array[0][i] = 1;
            }
        }

        for (int i = 1; i < len; i++) {
            for (int j = 0; j < col; j++) {
                int sz = j/(n + 1);
                int so = j%(n + 1);
                if(sz < zeros[i] || so < ones[i]){
                    array[i][j] = array[i - 1][j];
                } else {
                    array[i][j] = Math.max(array[i - 1][j], array[i - 1][j - (zeros[i] * (n + 1) + ones[i])] + 1);
                }
            }
        }

        return array[len - 1][col - 1];
    }
3、目标和

每个物品只能且必须操作一次,正好放满,求方案数

1、题目描述
目标和

给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 ‘+’ 或 ‘-’ ,然后串联起所有整数,可以构造一个 表达式 ,
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3

与方案V类似,将nums看作是各物品的体积,target看作是背包容量
题目前提:数组中的数 >= 0
target 与 -target 的方案数相同,所以取 target = abs( target )

2、题目分析,解法一

状态:

  • F[ i ][ j ] 表示[ 0,i ]的每件物品均操作(或装入(+),或取出(-)(不管有没有装入过))过,填满 j - sum 容量的方案数
  • 背包的容量边界是[ -sum,sum ]

状态转移: 关于 F[ i ][ j ]

  • i 物品必须参与操作,要么装入(+),要么取出(-)
  • 装入,如果 j - sum - nums[ i ] >= -sum,可以容纳 num[ i ],F[ i ][ j ] += F[ i - 1 ][ j - nums[ i ] ]
  • 取出,如果 j - sum + nums[ i ] <= sum,可以从背包中去除num[ i ],F[ i ][ j ] += F[ i - 1 ][ j + nums[ i ] ]

初始值:

  • 遍历 容量 k ∈ [ -sum,sum ],当 nums[ 0 ] = k时,F[ 0 ][ k + sum ] = 1
  • 如果num[ 0 ] = 0,nums[ 0 ] = k时,F[ 0 ][ k + sum ] = 2,这两种方案可以是 -0、0

返回结果:

  • F[ nums.length - 1 ][ target ]

在这里插入图片描述

3、代码处理

    public int findTargetSumWays(int[] nums, int target) {
        target = Math.abs(target);

        int val = 0;
        for (int num : nums) {
            val += num;
        }

        int[][] array = new int[nums.length][val*2 + 1];
        for (int i = -val; i <= val; i++) {
            if (Math.abs(i) == nums[0]) {
                if (nums[0] == 0) {
                    // -0 = 0,+0 = 0
                    array[0][i + val] = 2;
                } else {
                    array[0][i + val] = 1;
                }
            }
        }

        for (int i = 1; i < nums.length; i++) {
            for (int j = -val; j <= val; j++) {
                if(j - nums[i] >= -val){
                    array[i][j + val] += array[i - 1][j - nums[i] + val];
                }
                if(j + nums[i] <= val){
                    array[i][j + val] += array[i - 1][j + nums[i] + val];
                }
            }
        }

        return array[nums.length - 1][target + val];
    }

4、其他解法,简单高效
题目前提:数组中的数 >= 0
数组总和是 sum(>0),取负号的所有数的总和是 neg(> 0),则不取符号的所有数的总和是 sum - neg,
串联所有数,得到表达式:(sum-neg) - neg = target,
neg = (sum - target) / 2,如果 sum - target < 0 或 sum - target 为奇数,不可能得到要求的 target,直接返回 0,
从nums中选若干物品,放到容量为 neg 的背包(每件物品放一次)中,问恰好放满一共有几种方案(背包问题V一模一样)

4、零钱兑换

每个物品可以无限放,正好放满的所需的最小个数
1、题目描述
零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

此题目与背包问题V相似,但此处是求最少的硬币个数
硬币的面额就是物品的体积,amount就是背包容积

2、题目分析

状态:

  • F[ i ][ j ] 表示 [ 0,i ]的硬币组成 j 的最小硬币个数(无法组成为 -1)

状态转移:

  • coins[ i ] > j,或 F[ i ][ j - coins] == -1,说明不能放 i 硬币,此时 F[ i ][ j ] = F[ i - 1 ][ j ]
  • coins[ i ] <= j,且 F[ i ][ j - coins ] != -1,说明可以放 i 硬币,此时可以选择不放或放(放了就要在原硬币个数上 + 1),F[ i ][ j ] = max( F[ i - 1 ][ j ],F[ i ][ j - coins[ i ]] + 1)

初始状态:

  • 针对 coins[ 0 ],赋值 F[ 0 ][ j ](0 <= j <= amount)

返回结果:

  • F[ coins.length - 1 ][ amount ]

3、代码show

    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[][] array = new int[n][amount + 1];

        for (int i = 0; i <= amount; i++) {
            if(i % coins[0] == 0){
                array[0][i] = i / coins[0];
            } else {
                array[0][i] = -1;
            }
        }

        for (int i = 1; i < n; i++) {
            for (int j = 1; j <= amount; j++) {
                array[i][j] = array[i - 1][j];
                if(coins[i] <= j && array[i][j - coins[i]] != -1){
                    if(array[i - 1][j] == -1){
                        array[i][j] = array[i][j - coins[i]] + 1;
                    } else {
                        array[i][j] = Math.min(array[i - 1][j], array[i][j - coins[i]] + 1);
                    }
                }
            }
        }
        return array[n - 1][amount];
    }

4、空间优化 + 更换初始值

  • F[ i ][ j ] 依赖于 F[ i ][ k ](0 <= k <= j)及 F[ i - 1 ][ j ],可使用一维数组进行优化
  • 多设置一行,其面额为 0,这一行就作为初始值,F[ 0 ][ 0 ] = 0; F[ 0 ][ j ] = -1 ( j > 0)
    public int coinChange(int[] coins, int amount) {
        int n = coins.length;
        int[] array = new int[amount + 1];
        // 设置初始值
        for (int i = 1; i <= amount; i++) {
            array[i] = -1;
        }
        
        // 1 <= i <= n, i表示第i个硬币(coins[i - 1]), 遍历所有的硬币
        for (int i = 1; i <= n; i++) {
            for (int j = coins[i - 1]; j <= amount; j++) {
                if(array[j - coins[i - 1]] != -1){
                    if(array[j] == -1){
                        array[j] = array[j - coins[i - 1]] + 1;
                    } else {
                        array[j] = Math.min(array[j], array[j - coins[i - 1]] + 1);
                    }
                }
            }
        }

        return array[amount];
    }

千年帝都,盛世长安

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

威少总冠军

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值