算法基础复盘笔记Day09【动态规划】—— 背包问题

这篇具有很好参考价值的文章主要介绍了算法基础复盘笔记Day09【动态规划】—— 背包问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

❤ 作者主页:欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注点赞收藏评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉

第一章 背包问题

一、01背包问题

1. 题目描述

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 v i , w i v_i,w_i vi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N , V ≤ 1000 0<N,V≤1000 0<N,V1000
0 < v i , w i ≤ 1000 0<v_i,w_i≤1000 0<vi,wi1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

2. 思路分析

版本一: 二维数组
(1)状态 f[i][j] 定义: i i i 个物品,背包容量是 j j j 下的最优解(最大价值)

  • 当前的状态依赖于之前的状态,可以理解为从初始状态 f[0][0] = 0 开始决策,有 N 件物品,则需要 N 次决 策,每一次对第 i i i 件物品的决策,状态 f[i][j] 不断由之前的状态更新而来。

(2) 当前背包容量不够(j < v[i]),没得选,因此前 i i i 个物品最优解即为前 i − 1 i−1 i1 个物品最优解:

  • 状态转移方程:f[i][j] = f[i - 1][j]

(3) 当前背包容量够,可以选,因此需要决策选与不选第 i i i 个物品:

  • 选:f[i][j] = f[i - 1][j - v[i]] + w[i]
  • 不选:f[i][j] = f[i - 1][j]
  • 我们的决策是如何取到最大价值,因此以上两种情况取 最大值。

代码如下:

#include<bits/stdc++.h>

using namespace std;

const int N = 1010;
int v[N];    // 体积
int w[N];    // 价值 
int f[N][N];  // f[i][j], j体积下前i个物品的最大价值 

int main() 
{
    int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; i++) 
        cin >> v[i] >> w[i];

    for(int i = 1; i <= n; i++) 
        for(int j = 1; j <= m; j++)
        {
            //  当前背包容量装不进第i个物品,则价值等于前i-1个物品
            if(j < v[i]) 
                f[i][j] = f[i - 1][j];
            // 能装,需进行决策是否选择第i个物品
            else    
                f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
        }           

    cout << f[n][m] << endl;

    return 0;
}

版本二: 一维数组
将状态 f[i][j] 优化到一维 f[j],实际上只需要做一个等价变形。

为什么可以这样变形呢?我们定义的状态 f[i][j] 可以求得任意合法的 i i i j j j 最优解,但题目只需要求得最终状态 f[n][m] ,因此我们只需要一维的空间来更新状态。

(1) 状态 f[j] 定义:N 件物品,背包容量 j j j 下的最优解。

(2) 注意枚举背包容量 j j j 必须从 m m m 开始。

(3) 为什么一维情况下枚举背包容量需要逆序? 在二维情况下,状态 f[i][j] 是由上一轮i - 1的状态得来的,f[i][j]f[i - 1][j] 是独立的。而优化到一维后,如果我们还是正序,则有f[较小体积] 更新到f[较大体积],则有可能本应该用第i-1 轮的状态却用的是第 i 轮的状态。

(4) 例如,一维状态第 i 轮对体积为 3 的物品进行决策,则 f[7]f[4] 更新而来,这里的 f[4] 正确应该是 f[i - 1][4],但从小到大枚举j这里的 f[4] 在第i轮计算却变成了 f[i][4]。当逆序枚举背包容量j时,我们求 f[7] 同样由 f[4] 更新,但由于是逆序,这里的 f[4] 还没有在第 i 轮计算,所以此时实际计算的 f[4] 仍然是 f[i - 1][4]

(5) 简单来说,一维情况正序更新状态 f[j] 需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。

(6) 状态转移方程: f[j] = max(f[j], f[j - v[i]] + w[i]

for(int i = 1; i <= n; i++) 
    for(int j = m; j >= 0; j--)
    {
        if(j < v[i]) 
            f[i][j] = f[i - 1][j];  // 优化前
            f[j] = f[j];            // 优化后,该行自动成立,可省略。
        else    
            f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);  // 优化前
            f[j] = max(f[j], f[j - v[i]] + w[i]);                   // 优化后
    }  

实际上,只有当枚举的背包容量 j>= v[i] 时才会更新状态,因此我们可以修改循环终止条件进一步优化。

for(int i = 1; i <= n; i++)
    for(int j = m; j >= v[i]; j--)  
        f[j] = max(f[j], f[j - v[i]] + w[i]);

关于状态 f[j] 的补充说明:
二维下的状态定义 f[i][j] 是前 i i i 件物品,背包容量 j j j 下的最大价值。一维下,少了前 i i i 件物品这个维度,我们的代码中决策到第 i i i 件物品(循环到第i轮),f[j] 就是前i轮已经决策的物品且背包容量 j j j 下的最大价值。

因此当执行完循环结构后,由于已经决策了所有物品,f[j] 就是所有物品背包容量 j j j 下的最大价值。即一维 f[j] 等价于二维 f[n][j]


版本三: 优化输入
我们注意到在处理数据时,我们是一个物品一个物品,一个一个体积的枚举。

因此我们可以不必开两个数组记录体积和价值,而是边输入边处理。
代码如下:

#include<bits/stdc++.h>

using namespace std;

const int N = 1010;
int f[N];  

int main() 
{
    int n, m;   
    cin >> n >> m;

    for(int i = 1; i <= n; i++) {
        int v, w;
        cin >> v >> w;      // 边输入边处理
        for(int j = m; j >= v; j--)
            f[j] = max(f[j], f[j - v] + w);
    }

    cout << f[m] << endl;

    return 0;
}

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= n; i ++) cin >> v[i] >> w[i];
    
    for (int i = 1; i <= n; i ++)
        for (int j = m; j >= v[i]; j --)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    
    cout << f[m] << endl;
    
    return 0;
}

二、完全背包问题

1. 题目描述

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

i i i 种物品的体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 v i , w i v_i,w_i vi,wi,用空格隔开,分别表示第 i i i 种物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N , V ≤ 1000 0<N,V≤1000 0<N,V1000
0 < v i , w i ≤ 1000 0<v_i,w_i≤1000 0<vi,wi1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10

2. 思路分析

版本一: 二维数组
状态变量:f[i][j] 表示前 i i i 件物品放入容量为 j j j 的背包的最大价值。

当前背包容量为 j j j,我们要考虑 i i i 件物品能否放入?是否放入?

  1. 当前背包容量 j < w[i],不能放入,则 f[i][j] = f[i - 1][j]

  2. 当前背包容量 j > w[i],能放入,但要考虑代价

    • 若第 i i i 件物品不放入背包,则 f[i][j] = f[i - 1][j]
    • 若第 i i i 件物品放入背包,则 f[i][j] = f[i][j - w[i]] + c[i]

对于前 i i i 件物品,背包容量为 j - w[i] 时可能已经放入了第 i i i 件物品,容量为 j j j 时还可以再放入第 i i i 件物品,所以用 f[i][j - w[i]] 更新 f[i][j]

代码实现如下:

for (int i = 1; i <= n; i ++)
    {
        for (int j = 1; j <= m; j ++)
        {
            if (j < w[i])
                f[i][j] = f[i - 1][j];
            else
                f[i][j] = max(f[i - 1][j], f[i][j - w[i]] + c[i]);
        }
    }
    cout << f[n][m] << endl;

版本二: 一维数组
用一维数组 f[j] 只记录一行数据,让 j j j 值顺序循环,顺序更新 f[j]值。

for (int i = 1; i <= n; i ++)
    {
        for (int j = 1; j <= m; j ++)
        {
            if (j < w[i])
                f[j] = f[j];
            else
                f[j] = max(f[j], [j - w[i]] + c[i]);
        }
    }
    cout << f[m] << endl;

实际上,只有当枚举的背包容量 j>= v[i] 时才会更新状态,因此我们可以修改循环终止条件进一步优化。

 for (int i = 1; i <= n; i ++)
        for (int j = v[i]; j <= m; j ++)
            f[j] = max(f[j], f[j - v[i]] + w[i]);

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int f[N];

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= n; i ++) cin >> v[i]>> w[i];
    
    for (int i = 1; i <= n; i ++)
        for (int j = v[i]; j <= m; j ++)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    
    cout << f[m] << endl;
    
    return 0;
}

三、多重背包问题I

1. 题目描述

有 N 种物品和一个容量是 V 的背包。

i i i 种物品最多有 s i s_i si 件,每件体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行三个整数 v i , w i , s i v_i,w_i,s_i vi,wi,si,用空格隔开,分别表示第 i i i 种物品的体积、价值和数量。

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N , V ≤ 100 0<N,V≤100 0<N,V100
0 < 0< 0<v_i,w_i,s_i ≤ 100 ≤100 100

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

2. 思路分析

01背包:第 i i i 种物品可以取0件、取1件。
多重背包:第 i i i 种物品可以取0件、取1件、取2件…取 s i s_i si件。
多重背包问题转化为01背包求解:把第 i i i 种物品换成 s i s_i si 件01背包中的物品,每件物品的体积为 k ∗ v i k * v_i kvi,价值为 k ∗ w i k * w_i kwi(0 ≤ \leq k ≤ \leq s i s_i si)


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int n, m;
int v[N], w[N], s[N];
int f[N][N];

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= n; i ++) cin >> v[i] >> w[i] >> s[i];
    
    for (int i = 1; i <= n; i ++)
        for (int j = 0; j <= m; j ++)
            for (int k = 0; k <= s[i] && k * v[i] <= j; k ++)
                f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
    
    cout << f[n][m] << endl;
    
    return
}

四、多重背包问题 II

1. 题目描述

有 N 种物品和一个容量是 V 的背包。

i i i 种物品最多有 s i s_i si 件,每件体积是 v i v_i vi,价值是 w i w_i wi

求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入格式

第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有N 行,每行三个整数 v i , w i , s i v_i,w_i,s_i vi,wi,si,用空格隔开,分别表示第 i i i 种物品的体积、价值和数量。

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N ≤ 1000 0<N≤1000 0<N1000
0 < V ≤ 2000 0<V≤2000 0<V2000
0 < 0< 0<v_i,w_i,s_i ≤ 2000 ≤2000 2000
提示:

本题考查多重背包的二进制优化方法。

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

2. 思路分析

二进制优化方法的例子:
假设有50个苹果,现在要取 n n n 个苹果( n n n ≤ \leq 50),如何取?朴素的作家应该是将苹果一个一个拿出来,知道 n n n 个苹果被取出来。

二进制优化的思想就是:再假设有5个苹果和6只箱子,利用箱子继续某些预备工作,可以在每个箱子中放 2 k 2^k 2k(k ≥ \geq 0)个苹果,也就是1、2、4、8、16、19(剩余的数),取任意 n n n 个苹果时,只需要推出几只箱子就可以了。例如:取20个苹果,只需要拿出2个箱子(1、19),这样的话原来20次操作的现在变成2次操作就可以了。

二进制拆分思想:
将第 i i i 种物品拆分成若干件物品,每件物品的体积和价值乘以一个拆分系数(1, 2 1 2^1 21, 2 2 2^2 22 2 k − 1 2^{k-1} 2k1, s i − 2 k + 1 s_i - 2^k + 1 si2k+1),就可以转化成01背包的物品求解。

例如: s i = 12 s_i = 12 si=12,拆分系数为 1 , 2 , 4 , 5 1,2,4,5 1,2,4,5,转化成4件01背包的物品: ( v i , w i ) (v_i, w_i) (vi,wi) ( 2 v i , 2 w i ) (2v_i, 2w_i) (2vi,2wi) ( 4 v i , 4 w i ) (4v_i, 4w_i) (4vi,4wi) ( 5 v i , 5 w i ) (5v_i, 5w_i) (5vi,5wi)


3. 代码实现

#include <bits/stdc++.h>

using namespace std;

//逐一枚举到最大是 N * logN
const int N = 12010, M = 2010;

int n, m;
int v[N], w[N], f[M];

int main()
{
    cin >> n >> m;
    
    int cnt = 0; //分组的类别
    for (int i = 1; i <= n; i ++)
    {
        int a, b, s;
        cin >> a >> b >> s;
        
        int k = 1; //组别里面的个数
        while (k <= s)
        {
            cnt ++; //组别先增加
            v[cnt] = a * k; //整体体积
            w[cnt] = b * k; //整体价值
            s -= k; //物品的个数要减少k个
            k *= 2; //组别里面的个数增加
        }
        
        //剩余的一组
        if (s >= 0)
        {
            cnt ++;
            v[cnt] = a * s;
            w[cnt] = b * s;
        }
    }
    
    n = cnt; //枚举次数由个数变成组别数
    
    //01背包一维优化
    for (int i = 1; i <= n; i ++)
        for (int j = m; j >= v[i]; j --)
            f[j] = max(f[j], f[j - v[i]] + w[i]);
    
    cout << f[m] << endl;
    
    return 0;
}

五、分组背包问题

1. 题目描述

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 v i j v_{ij} vij,价值是 w i j w_{ij} wij,其中 i i i 是组号, j j j 是组内编号。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

输出最大价值。

输入格式

第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量。

接下来有 N 组数据:

  • 每组数据第一行有一个整数 S i S_i Si,表示第 i i i 个物品组的物品数量;
  • 每组数据接下来有 S i S_i Si 行,每行有两个整数 v i j , w i j v_{ij},w_{ij} vij,wij,用空格隔开,分别表示第 i i i 个物品组的第 j j j 个物品的体积和价值;

输出格式

输出一个整数,表示最大价值。

数据范围

0 < N , V ≤ 100 0<N,V≤100 0<N,V100
0 < S i ≤ 100 0<S_i≤100 0<Si100
$0< v i j , w i j v_{ij},w_{ij} vij,wij≤100$

输入样例

3 5
2
1 2
2 4
1
3 4
1
4 5

输出样例:

8

2. 思路分析

最大价值应该是物品组 i i i 和背包容量 j j j 的函数,用 f[i][j] 表示前 i i i 组物品,能放入容量为 j j j 的背包的最大价值。

朴素算法应该是循环物品组,循环背包容量,对第 i i i 组物品,容量为 j j j 的背包,有 s + 1 s + 1 s+1 种选法,

max(f[i - 1][j], f[i - 1][j - v 1 v_1 v1] + w 1 w_1 w1, f[i - 1][j - v 2 v_2 v2] + w 2 w_2 w2,…,f[i - 1][j - v 5 v_5 v5] + w 5 w_5 w5)

代码如下:

	// 背包问题,朴素算法
    for (int i = 1; i <= n; i ++) //物品
        for (int j = 1; j <= m; j ++) //体积
            for (int k = 0; k < s[i]; k ++) //决策
                if (v[i][k] <= j)
                    f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
    
    cout << f[n][m] << endl;

可以优化为一维数组:

    for (int i = 1; i <= n; i ++) //物品
        for (int j = m; j >= 0; j --) //体积
            for (int k = 0; k < s[i]; k ++) //决策
                if (v[i][k] <= j)
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
    
    cout << f[m] << endl;

3. 代码实现

#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int n, m;
int v[N][N], w[N][N], s[N];
int f[N];

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= n; i ++)
    {
        cin >> s[i];
        for (int j = 0; j < s[i]; j ++)
            cin >> v[i][j] >> w[i][j];
    }
    
    for (int i = 1; i <= n; i ++) //物品
        for (int j = m; j >= 0; j --) //体积
            for (int k = 0; k < s[i]; k ++) //决策
                if (v[i][k] <= j)
                    f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
    
    cout << f[m] << endl;
    
    return 0;
}

创作不易,如果有帮助到你,请给文章点个赞和收藏,让更多的人看到!!!
关注博主不迷路,内容持续更新中。文章来源地址https://www.toymoban.com/news/detail-420805.html

到了这里,关于算法基础复盘笔记Day09【动态规划】—— 背包问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • acwing算法基础之动态规划--背包问题

    (零) 背包问题描述:有 N N N 个物品,每个物品的体积是 v i v_i v i ​ ,价值是 w i w_i w i ​ ,现有容量是 V V V 的背包,求这个背包能装下的物品的最大价值。 01背包问题:每个物品只有1个。 完全背包问题:每个物品有无穷多个。 多重背包问题:第 i i i 个物品有 s i s_i s

    2024年02月05日
    浏览(37)
  • 力扣算法刷题Day42|动态规划:01背包问题 分割等和子集

    力扣题目:01背包问题(二维数组) 刷题时长:参考题解 解题方法:动态规划 + 二维dp数组 复杂度分析 时间 空间 问题总结 理解递推公式困难 本题收获 动规思路:两层for循环,第一层i遍历物品,第二层j枚举背包容量以内所有值 确定dp数组及下标的含义:dp[i][j] 表示从下标

    2024年02月13日
    浏览(41)
  • 力扣算法刷题Day44|动态规划:完全背包问题 零钱兑换II 组合总和Ⅳ

    力扣题目:#518.零钱兑换II(完全背包组合问题) 刷题时长:7min 解题方法:动态规划(完全背包) 复杂度分析 时间复杂度: O(mn),其中 m 是amount,n 是 coins 的长度 空间复杂度: O(m) 问题总结 对递推公式的理解 本题收获 题意转换:纯完全背包是凑成背包最大价值是多少,而本

    2024年02月13日
    浏览(30)
  • Python 算法基础篇:背包问题的动态规划解法

    背包问题是计算机科学中一个重要的组合优化问题,动态规划是解决该问题的高效算法技术。本篇博客将重点介绍背包问题的动态规划解法,包括状

    2024年02月16日
    浏览(30)
  • 动态规划day05(背包问题)

    力扣题目链接(opens new window) 题目难度:中等 有一堆石头,每块石头的重量都是正整数。 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x = y。那么粉碎的可能结果如下: 如果 x == y,那么两块石头都会被完全粉碎; 如果 x !=

    2024年01月17日
    浏览(29)
  • 动态规划day04(01背包问题)

    01背包问题(二维数组和滚动数组) 本题力扣上没有原题,大家可以去卡码网第46题 (opens new window)去练习,题意是一样的。 《代码随想录》算法视频公开课 (opens new window):带你学透0-1背包问题! (opens new window),相信结合视频再看本篇题解,更有助于大家对本题的理解 。 这周

    2024年01月18日
    浏览(34)
  • day48算法训练|动态规划part09

    1. dp数组(dp table)以及下标的含义 dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i] 。 2.递推公式 决定dp[i]的因素就是第i房间偷还是不偷。 如果偷第i房间,那么dp[i] = dp[i - 2] + nums[i] ,即:第i-1房一定是不考虑的,找出 下标i-2(包括i-2)以内的房屋,最多

    2024年01月16日
    浏览(32)
  • Day 42 算法记录|动态规划 09 (打家劫舍)

    1.dp[i]:考虑下标i(包括i)以内的房屋,最多可以偷窃的金额为dp[i]。 2.dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]); 3.初始化,dp[0] 和 dp[1],dp[0] 一定是 nums[0],dp[1] = max(nums[0], nums[1]); 3.遍历顺序,dp[i] 是根据dp[i - 2] 和 dp[i - 1] 推导出来的,那么一定是从前到后遍历! 进一步对滚动数组

    2024年02月15日
    浏览(26)
  • Leetcoder Day39| 动态规划part06 完全背包问题

    有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。 每件物品都有无限个(也就是可以放入背包多次) ,求解将哪些物品装入背包里物品价值总和最大。 示例: 背包最大重量为4。 物品为: 重量 价值 物品0 1 15 物品1 3 20 物品2 4 30 每

    2024年03月25日
    浏览(29)
  • 算法学习笔记(动态规划——01背包)

    先来聊聊动态规划,动态规划是分治法的一种体现,把一个问题分解成若干个子集,通过当前状态,经过操作得到下一个状态,最后得到最优问题解的一种方法。 步骤: 设定状态,保存状态 根据状态设定转移方程 确定边界 其中的01背包解决的是关于选择的动态规划问题,

    2024年03月25日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包