[AcWing蓝桥杯]之复杂DP(C++题解)

目录

鸣人的影分身(线性DP)

DFS解法

 DP(完全背包)

思维解法递归解法

包子凑数(完全背包+数论)

糖果(01背包问题)

密码脱落(区间DP+数学)

括号配对(区间DP)

生命之树(树形DP)

旅游规划(树形DP)


前景提要:

(1)dp数组的含义:这个dp数组代表的意义是什么,[i][j]又分别代表什么意思

(2)dp数组的属性:包括最大值,最小值,方案数,次数,即dp数组本身存的数

(3)dp数组的初始化:根据它的含义进行合理的初始化

(4)dp数组的遍历顺序:是正序从前往后,还是后续遍历防止被重复覆盖

(5)dp数组的递推公式:从最后一步反推,即要得到这一步,需要上一步进行的操作

(6)dp数组的返回值:即题目要求的是dp数组的什么,要根据含义来


鸣人的影分身(线性DP)

1050. 鸣人的影分身 - AcWing题库

思路解析:

透过题目看本质,这道题的本质是:

给出一个和,给出一个份数,求出“和”可以被分成多少个份数大小的集合

举个例子:总和为:7   份数为:3

那么就有:(注意顺序不同,集合内的数字相同被视为一种情况)

(7,0,0)

( 6,1,0)  

(5,2,0) (5,1,1)

(4,3,0) (4,2,1)

(3,3,1)  (3,2,2)

DFS解法

相信看完例子解释的你,已经想到了DFS,来暴力搜索

DFS代码如下:

DFS的核心之一就是参数的意义

(1)首先要输入总能量,和分身的数量

那么在向下搜索的过程中:

(2)思考深搜过程:

<1>需要一个标志变量,来标记当前从这个标志变量开始枚举,也就是变量start

这个就类似于(1~5)个数中(集合大小为3个数)的组合

<2>边界条件,当给被赋予能量的分身数量==给出的分身数量 且剩余能量为0

<3>剪枝,当枚举过程中,被赋予能量的分身数量大于给定的分身数量

#include<iostream>
using namespace std;
int m, n;
int res;
// t:剩余的能量  start:下一个分身的值  state:已经完成的分身
void dfs(int t, int start, int state)
{
	if (state == n && t == 0)//已经完成的分身数量==n  且剩余的能量为0 即返回
	{
		res++;//记录数量
	}
    //可不写“==”
	if (state >= n) return;//已经完成的分身数量>=n,也返回(就算还有剩余能量)

	for (int i = start; i <= t; i++)
	{
		dfs(t - i, i, state + 1);//剩余的能量-i,下一个从i开始枚举,分身的数量++
	}
}

int main()
{
	int T;
	cin >> T;
	while (T--)
	{
		cin >> m >> n;
		res = 0;
		dfs(m, 0, 0);//初始剩余的能量为:m  
		cout << res << endl;
	}

	return 0;
}

 DP(完全背包)

有没有一种可能这个是完全背包,即每个数可以被放入背包无限次

(1)dp数组的含义:总和为i,划分为j个数的所有情况

(2)dp数组的属性:符合题目条件的次数

(3)dp数组的递推公式:观察上面给出的例子可知:

对于最后一步有:划分为j个数情况:

<1>当i<j时,也就是必然有影子被分为0的情况

划分为j个数的情况==划分j-1个数的情况+最后一列放0的情况

<2>当i>=j时,有两种情况:

当最小的数为不为0的情况,那么方案数==总和i-j个1的情况+最小的数为0的情况

·······1:有影子被分到的最小能量为0(最小的数为0的情况被<1>处理了)

·······2:影子的被分到的最小能量不为0

(4)dp数组的初始化:由递推公式可知:dp[i][0]的情况都是1

#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 11;

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        int n, m;
        scanf("%d%d", &m, &n);

        int f[N][N] = {0};//dp数组的含义:总和为i,划分为j个数的所有情况
        f[0][0] = 1;//当总数为0,划分为0个,情况数为:1
        /*问题:
        for(int i=0;i<m;i++) 
            f[i][0]=1;*/
            
        for (int i = 0; i <= m; i ++ )
            for (int j = 1; j <= n; j ++ )
            {
                f[i][j] = f[i][j - 1];//划分为j个数的情况==划分j-1个数的情况+最后一列放0的情况
                if (i >= j) f[i][j] += f[i - j][j];//当最小的数为不为0的情况,那么方案数==总和i-j个1的情况+最小的数为0的情况
            }

        printf("%d\n", f[m][n]);
    }

    return 0;
}

思维解法递归解法

转换为:n个苹果放m个盘的经典问题

#include<cstdio>
#include<algorithm>
#include<iostream>

using namespace std;

int f(int x, int y) {
    if (x == 0) return 1;//没有苹果,全部盘子为0
    if (y == 0) return 0;//没有盘子,没法放
    if (y > x) {//盘子数大于苹果数,至多只能x个盘子上都放一个 
        return f(x, x);
    }
    return f(x - y, y) + f(x, y - 1);//盘子数小于等于苹果数 -> 分类讨论: 有盘子为空,没有盘子为空
//有盘子为空的时候即至少有一个盘子为空,f(x,y-1);没有盘子为空即最少每个盘子都有一个,f(x-y,y)     
}

int main() {
    int t, n, m;//n个苹果分到m个盘子里去,运行盘子为空 
    cin >> t;
    while (t--) {
        cin >> n >> m;
        cout << f(n, m) << endl;
    }
    return 0;
}

包子凑数(完全背包+数论)

活动 - AcWing

本题与:活动 - AcWing(买不到的糖果有异曲同工之妙)

复习一下:

给定a,b,若 d=gcd(a,b)>1 ,则一定不能凑出最大数

如果 a,b均是正整数且互质,那么由 ax+by,x≥0,y≥0不能凑出的最大数是 (a−1)(b−1)−1

 在买不到的糖果那道题中,是给定两个数,求出这两个数不能组成的最大的数

而在本题中,给定的是任意N个数,求的是这N个数,不能组成的数的个数

DP分析:

 

(1)dp数组的含义:从前i项物品任意个,总和为j

(2)dp数组的属性:能够达到说明非空,不能达到说明为空

(3)dp数组的递推公式:

由于是完全背包,所以对于一个物品来说,可以选择多次,假设选择这个物品k次:

那么就将j分成了两部分:原本的部分(减去k件该物品的部分)+k件该物品的部分

那么只要知道那原本的部分是否可以满足,如果可以被满足,那么就为true,所以是用的或运算符|,只要其中有一个部分可以满足,那么再挑选这个部分那么必然也是满足的

(4)dp数组的初始化:

由递推公式可知:dp[0][0]应被初始化为true,因为从0个物品中总和为0,是可以满足的

那么对于不选择该物品的情况,就应该让它等于上一个选择物品的情况

(5)dp数组的遍历顺序:

正序两层for循环即可

(6)dp数组的返回值:

统计不满足的个数即可

#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 10010;//历史遗留问题:根据数据范围猜的

int a[110];
bool f[110][N];

int gcd(int a, int b)
{
    return b ? gcd(b, a % b) : a;
}

int main()
{
    int n;
    scanf("%d", &n);
    int d = 0;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        d = gcd(d, a[i]);
    }

    if (d != 1) puts("INF");
    else
    {
        f[0][0] = true;
        for (int i = 1; i <= n; i++)
            for (int j = 0; j < N; j++)
            {
                f[i][j] = f[i - 1][j];
                if (j >= a[i]) f[i][j] |= f[i][j - a[i]];
            }

        int res = 0;
        for (int i = 0; i < N; i++)
            if (!f[n][i])
                res++;

        printf("%d\n", res);
    }

    return 0;
}

糖果(01背包问题)

1047. 糖果 - AcWing题库

思路解析:

经典的01背包问题:即每个糖果只能选择一次

(1)dp数组的含义:所有从前i个物品中选,且总和除以k为j的糖果数量

(2)dp数组的属性:糖果的数量

(3)dp数组的递推公式:对于最后一步有:选和不选两种情况,两者取最大即可

<1>不选择该包糖果,那么此时的糖果数量还是等于上一个的数量

<2>选择该包糖果,那么此时糖果的数量等于上一个的糖果数量+该包糖果的数量

注意:对于选择该包糖果的情况,需要对余数j进行正数处理

(4)dp数组的初始化:y总说,对于没有意义的,要么初始化为负无穷要么初始化为正无穷,那么这里由于是要求最多的糖果数量,那么一定是要初始化为负无穷的

那么对于有意义的值:dp[0][0],也就是前0个物品中,总和必定为0,分给k个,那么必然也是0,糖果的总数也是0,所以将dp[0][0]初始化为0

(5)dp数组的遍历顺序:注意看递推公式:由它的左上角推出,所以只需正序遍历两层即可

(6)dp数组的返回值:由于题目要求的能整除k的糖果总数,那么根据定义,j一定为0,要使糖果的总数最大,那么一定是从前n个包中选,所以返回值是dp[n][0]

#include<cstdio>
#include<cstring>
using namespace std;
const int N = 110;
int n, k;
int f[N][N];

int max(int x, int y)
{
	return x > y ? x : y;
}

int main()
{
	scanf("%d%d", &n, &k);

	memset(f, -0x3f, sizeof f);

	f[0][0] = 0;//所有从前i个物品中选,且总和除以k为j的糖果数量
	//求最大数量

	for (int i = 1; i <= n; i++)//外层遍历物品
	{
		int w;
		scanf("%d", &w);
		for (int j = 0; j < k; j++)//内层遍历背包容量
		{			//不选:等于上一个的状态	 //选:
			f[i][j] = max(f[i - 1][j], f[i - 1][(j + k - w % k) % k] + w);
		}
	}
	printf("%d\n", f[n][0]);//寻找余数为0的,即能别整除的

	return 0;
}

密码脱落(区间DP+数学)

1222. 密码脱落 - AcWing题库

题目概述:

给定一个字符串,请判断原来的字符串通过多少次种子脱落,而得到的现在的字符串

题意转化:

由于脱落的字母可以从任意位置脱落,所以只需求出给定字符串的最大回文串,那么用给定字符串的长度--该串中的最大回文串,那么即可得到孤立无援的字母,那么再对应从任意位置中加入该字母,即可凑成原来的回文串

 区间DP:

(1)dp数组的含义:在{i,j}区间内的范围内,最大回文串的长度

(2)dp数组的属性:长度

(3)dp数组的递推公式

​if (s[l] == s[r]) f[l][r] = f[l + 1][r - 1] + 2;(如果左右端点相等,那么就把这两个端点加入)

(左右两边向外扩张)
if (f[l][r - 1] > f[l][r]) f[l][r] = f[l][r - 1];(寻找最大的回文子串)

if (f[l + 1][r] > f[l][r]) f[l][r] = f[l + 1][r];(寻找最大的回文子串)

(4)dp数组的初始化:对于单个字母,独立成一个长度为1的回文串,所以遍历长度的同时,将len==1,全部赋值为dp[i][j]=1

(5)dp数组的遍历顺序:滑动窗口的思想,先枚举窗口的大小,再枚举左端点,边界条件为右端点小于数组的长度即可

(6)dp数组的返回值:最长的回文子串一定包含在dp[0][n-1](注意定义)

#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1010;
char s[N];
int f[N][N];

int main()
{
	scanf("%s", s);
	int n = strlen(s);

	for(int len=1;len<=n;len++)
		for (int l = 0; l + len - 1 < n; l++)
		{
			int r = l + len - 1;
			if (len == 1) f[l][r] = 1;
			else
			{
				if (s[l] == s[r]) f[l][r] = f[l + 1][r - 1] + 2;
				if (f[l][r - 1] > f[l][r]) f[l][r] = f[l][r - 1];
				if (f[l + 1][r] > f[l][r]) f[l][r] = f[l + 1][r];
			}
		}
	printf("%d\n", n - f[0][n - 1]);//得到有多少个单独的字母个数,那么就只需再+上即可配对

	return 0;
}

括号配对(区间DP)

1070. 括号配对 - AcWing题库

题意概述:给定括号序列,通过在任意位置添加括号序列使得该括号序列合法

本题与上题(密码脱落)不同的点在于:不能单纯地通过判断左右端点满足括号的条件就同时向外扩展,要注意特例:比如说:  [ ] ( [ ]

dp分析:

(1)dp数组的含义:向字符串[i][j]范围内通过添加括号,使其变成合法括号的集合

(2)dp数组的属性:添加括号的最小操作数

(3)dp数组的递推公式:

<1>首先应该确定一个判断其合法的函数,即前面为 [  (   那么后面就应该为 )  ]

<2>如果满足上述的判断,且i,j的间距小于等于1,那么相当于这个是确实符合条件了,没有特例,可同时向左右两边扩展

<3>如果满足上述的判断,但是i,j的间距大于1,那么就要在[i,j]范围内枚举间隔点,将其分割为[i,k]  [k+1,r],这里是找到最小操作数的核心

(4)dp数组的初始化:对于单独的括号,最小操作数就是添加一个与其相对应的括号,即最小操作数是1,对于没有意义的地方,由于是求最小,所以初始化为最大

(5)dp数组的遍历顺序:一个从后向前遍历,一个从前向后遍历,这样是保证【i,j】是左右端点的同时,减少一些不必要的重复操作,当然也可以i从前,j=i,向前遍历

(6)dp数组的返回值,同理:答案必定存在于整个字符串中

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
int dp[N][N];
char s[N];

bool match(int i,int j)
{
    if(s[i]=='[' && s[j]==']') return true;
    if(s[i]=='(' && s[j]==')') return true;
    return false;
}

int main()
{
    scanf("%s",s);
    int n=strlen(s);
    
    for(int i=0;i<N;i++)
    {
        for(int j=0;j<N;j++)
        {
            dp[i][j]=1e9;
        }
    }
    
    for(int i=n-1;i>-1;i--)
    {
        for(int j=i;j<n;j++)
        {
            if(i==j)
            {
                dp[i][j]=1;
                continue;
            }
            if(match(i,j))
            {
                dp[i][j]=min(dp[i][j],(i==j-1?0:dp[i+1][j-1]));
            }
            for(int k=i;k<=j;k++)
            {
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
            }
        }
    }
    cout<<dp[0][n-1]<<endl;
    
    return 0;
}

生命之树(树形DP)

1220. 生命之树 - AcWing题库

 树形DP:

(1)dp数组的定义:表示以u为根节点的子树中所有连通块的的权值的最大值

(2)dp数组的属性:最大值

(3)dp数组的递推公式:如果大于0,则把它加入dp数组中,核心就是贪心的思想,因为要求的是连通块的最大值

 核心是理解下面这个图以及dp数组的含义

 dp[1]是:以1为根节点,包括1的所有联通块的最大值

dp[2]是:以2为根节点,包括2的所有连通块的最大值(注意此时不包括1)

dp[3]是:以3为根节点,包括3的所有连通块的最大值(注意此时不包括1)

同理dp[4]=4  dp[5]=5(也就是说不能向上寻找只能向下寻找)

所以dfs深搜的第一步是:将f[u]=w[u]   (将该根节点的值存入dp数组)

之后才是向下搜索,注意此时的f[j]只是可能是的单个节点的值,它指的是这个节点之下包括这个节点的值,所以是大于0,才把它加入进去

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

typedef long long LL;
const int N = 1e5 + 10;

int n;
LL f[N];
int w[N];
int h[N], e[N * 2], ne[N * 2], idx;

void add(int a,int b){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx ++;
}

//第二个参数是记录父节点,防止向回走
void dfs(int u,int fa){
    f[u] = w[u];

    for (int p = h[u] ; p != -1 ; p = ne[p])//单链表的遍历
    {
        int j = e[p];
        if (j != fa)//防止走回头路
        {
            dfs(j, u);//取出节点所存的值作为下一个的当前节点,当前节点作为下一个节点的父亲
            f[u] += max(0ll, f[j]);//连通块++
        }
    }
}

int main()
{
    //读入
    cin >> n;
    for (int i = 1 ; i <= n ; i ++) cin >> w[i];
    memset(h, -1, sizeof h);
    for (int i = 0 ; i < n - 1 ; i ++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b);add(b, a);//无向图
    }

    //树形DP
    dfs(1, -1);//当前节点,当前节点的父亲

    LL res = -1e10;
    for (int i = 1 ; i <= n ; i ++) res = max(res, f[i]);

    cout << res << endl;
    return 0;
}

旅游规划(树形DP)

1078. 旅游规划 - AcWing题库

题意转换:求树的直径上的点

活动 - AcWing(大臣的旅费)中,我们知道了一种求树的直径的方法:

即:

 代码实现:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstdio>
#include<vector>
using namespace std;

const int N = 100010;
int n;
struct  Edge
{
	int id, w;//指向的那条边,和这条边的边长
};
vector<Edge> h[N];
int dist[N];

void dfs(int u, int father, int distance)
{
	dist[u] = distance;//记录长度

	for (auto node : h[u])//想象成单链表
	{
		if (node.id != father)//保证不会往回走,保证向下走
			dfs(node.id, u, distance + node.w);//当前节点,u则为当前节点的父亲,距离增加
	}
}

int main()
{
	scanf("%d", &n);
	for (int i = 0; i < n - 1; i++)
	{
		int a, b, c;
		scanf("%d%d%d", &a, &b, &c);
		h[a].push_back({ b,c });
		h[b].push_back({ a,c });
	}

	dfs(1, -1, 0);//第一次传参:当前节点,当前节点的父亲,当前节点到父节点的距离

	int u = 1;
	for(int i=1;i<=n;i++)
		if (dist[i] > dist[u])//寻找从1号点到最远能到的路径距离
		{
			u = i;
		}

	dfs(u, -1, 0);//上述的最远的能到的距离作为头,从新开始寻找离它的最远距离

	for(int i=1;i<=n;i++)
		if (dist[i] > dist[u])//同理
		{
			u = i;
		}

	int s = dist[u];//u到最远的距离

	printf("%lld\n", s * 10 + s * (s + 1ll) / 2);

	return 0;
}

 

 

而在本题中,采用了另外一种做法,因为要求树的直径经过的点:

对于一个节点而言,要证明它是否在树的直径上,那么只需证明以它为节点,它的最大值和次大值的和是否等于树的直径即可,下面就是对跟节点的不同而分的情况:

<1>当树为一开始的根节点(头部)时即:

那么即是它:向下寻找的最大值和次大值之和,此时向下的最大值就是d1,次大值就是d2

注意:次大值不一定比最大值小,它是除了最大值以外,最大的数

<2>当树为中间的根节点时即:

那么向下扩展的同时,也需要向上扩展,此时就是数组up,同时需要记录:

如果j向下走

因为d1[j]是不能往回走的(一开始是从i-->j),所以此时是要用d2[j]来向下走

如果不是已经走过的点,那么就可以用d1[j]继续走

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 200010, M = N * 2;
int h[N], e[M], ne[M], idx;
int n, maxd;
int d1[N], d2[N], up[N], p1[N];
//d1:每个节点向下走的最长路径
//d2:每个节点向下走的次长路径的长度
//up数组:表示每个节点向上走能走的最远距离
//p1存下u节点往下最大路走的子节点
//所有结点的最长路径和次长路径之和的最大值就是树的直径

void add(int a, int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

void dfs_d(int u, int father)//向下寻找
{
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j != father)//防止向父亲方向寻找(无向图:每个节点方向都有可能是父亲方向)
        {
            dfs_d(j, u);//向下一层寻找
            int distance = d1[j] + 1;//距离+1
            if (distance > d1[u])//如果距离大于了当前向下寻找的最大距离
            {
                d2[u] = d1[u];//次大节点距离=上一个最大节点的最大距离
                d1[u] = distance;//最大距离更新为distance
                p1[u] = j;//记录已经走过的节点
            }
            else if (distance > d2[u]) d2[u] = distance;
        }
    }
    maxd = max(maxd, d1[u] + d2[u]);
}

void dfs_u(int u, int father)//向上寻找
{
    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (j != father)
        {
            up[j] = up[u] + 1;//up数组和父节点有关
            if (p1[u] == j) //说明从j向下走是从u向下走最长路径
            {
                up[j] = max(up[j], d2[u] + 1);//用第二长的路径更新up数组
            }
            else up[j] = max(up[j], d1[u] + 1);//说明从j向下走不是从u向下走的最长路径,用最长的路径更新up数组
            dfs_u(j, u);//从上往下算
        }
    }
}

int main()
{
    memset(h, -1, sizeof h);
    scanf("%d", &n);
    for (int i = 0; i < n - 1; i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);//无向图
        add(b, a);
    }

    dfs_d(0, -1);
    dfs_u(0, -1);

    for (int i = 0; i < n; i++)
    {
        int dx[3] = { d1[i],d2[i],up[i] };
        sort(dx, dx + 3);
        if (dx[2] + dx[1] == maxd) printf("%d\n", i);//如果这个点的扩展最大和次大的和==树的直径,那么这个点必然在直接上
    }
    return 0;
}

  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
引用\[1\]:这段代码是一个解决LeetCode上某个题目的C++实现,具体是一个双指针的解法。该题目是计算一个数组中的积水量。代码中使用了两个指针分别指向数组的左右边界,然后通过比较左右指针所指向的元素的大小,来确定当前位置的积水量。具体的计算方法是,如果左指针所指向的元素小于右指针所指向的元素,则对左指针的左边进行操作,如果左指针所指向的元素大于等于右指针所指向的元素,则对右指针的右边进行操作。在每一次操作中,都会更新左边的最大值和右边的最大值,并计算当前位置的积水量。最后返回总的积水量。\[1\] 引用\[2\]:这段代码是另一个解决LeetCode上某个题目的C++实现,具体是一个深度优先搜索的解法。该题目是计算一个二维网格中从起点到终点的可行路径数量。代码中使用了递归的方式进行深度优先搜索,从起点开始,每次向下或向右移动一步,直到到达终点。在每一步移动中,会判断当前位置是否有障碍物,如果有障碍物则返回0,如果到达终点则返回1,否则继续递归搜索下一步的位置。最后返回总的可行路径数量。\[2\] 引用\[3\]:这段代码是另一个解决LeetCode上某个题目的C++实现,具体是一个动态规划的解法。该题目是计算一个数组中的积水量。代码中使用了动态规划的思想,通过遍历数组中的每个元素,分别计算该元素左边和右边的最大值,并计算当前位置的积水量。最后返回总的积水量。\[3\] 综上所述,这三段代码分别是解决LeetCode上不同题目的C++实现,分别使用了双指针、深度优先搜索和动态规划的方法来解决问题。 #### 引用[.reference_title] - *1* *3* [Leetcode 热题100 42.接雨水(C++ 多种解法,错过可惜)](https://blog.csdn.net/qq_51933234/article/details/124637883)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [[C++]Leetcode 不同路径 || 解题思路及详解](https://blog.csdn.net/weixin_62712365/article/details/123951736)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值