浅谈倍增法求解LCA

Luogu P3379 最近公共祖先

原题展现

题目描述

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入格式

第一行包含三个正整数 N , M , S N,M,S N,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N − 1 N-1 N1 行每行包含两个正整数 x , y x, y x,y,表示 x x x 结点和 y y y 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 M M M 行每行包含两个正整数 a , b a, b a,b,表示询问 a a a 结点和 b b b 结点的最近公共祖先。

输出格式

输出包含 M M M 行,每行包含一个正整数,依次为每一个询问的结果。

样例输入 #1

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

样例输出 #1

4
4
1
4
4

提示

对于 30 % 30\% 30% 的数据, N ≤ 10 N\leq 10 N10 M ≤ 10 M\leq 10 M10

对于 70 % 70\% 70% 的数据, N ≤ 10000 N\leq 10000 N10000 M ≤ 10000 M\leq 10000 M10000

对于 100 % 100\% 100% 的数据, N ≤ 500000 N\leq 500000 N500000 M ≤ 500000 M\leq 500000 M500000

样例说明:

该树结构如下:

第一次询问: 2 , 4 2, 4 2,4 的最近公共祖先,故为 4 4 4

第二次询问: 3 , 2 3, 2 3,2 的最近公共祖先,故为 4 4 4

第三次询问: 3 , 5 3, 5 3,5 的最近公共祖先,故为 1 1 1

第四次询问: 1 , 2 1, 2 1,2 的最近公共祖先,故为 4 4 4

第五次询问: 4 , 5 4, 5 4,5 的最近公共祖先,故为 4 4 4

故输出依次为 4 , 4 , 1 , 4 , 4 4, 4, 1, 4, 4 4,4,1,4,4

解析

本题是 LCA 的模板

LCA 的做法很多,比如暴力跳,倍增

暴力跳

让深度大的一点不断向上跳,直到两点深度相等

如果两点深度相同但是并不相等,可以两点一起跳

在随机数据下表现优异,因为树会比较平衡,所以近似 O ( log ⁡ n ) O(\log n) O(logn)

通常会被卡成单次 O ( n ) O(n) O(n),其实不难构造,可以构造一个深度大的树(比如链)

本人出的一道题思想类似这样,不过这道题保证了平衡

倍增法

考虑一次跳多一点

f a u , k fa_{u,k} fau,k表示距离 u u u的边数为 2 k 2^k 2k的祖先节点则 f a u , k = f a f a u , k − 1 , k − 1 fa_{u,k}=fa_{fa_{u,k-1},k-1} fau,k=fafau,k1,k1可以通过dfs求出 f a fa fa

如果求LCA,我们可以很快让两点来到相同的深度

考虑求两点深度差,将差二进制拆分,每次跳一个 2 2 2的幂,时间复杂度 O ( log ⁡ n ) O(\log n) O(logn)

当然,没必要真的二进制拆分,因为我们要知道是 2 2 2的几次幂,所以用cmathlog2更加方便

这里有一个优化:用 O ( n ) O(n) O(n)的时间复杂度递推求出log2的值

然后,如果两点深度相同不相等,有一个自认为巧妙的方法求解

一个性质:如果两点跳到LCA了,继续向上跳依然相等(易证)

如果两点向上跳不相等,那么一定可以继续跳

于是想到一个办法:尝试枚举 i i i 31 31 31 0 0 0,表示尝试跳 2 i 2^i 2i

如果向上跳不相同的话,就向上跳,这样,枚举完,LCA就是 f a x , 0 fa_{x,0} fax,0

核心代码如下,首先是预处理

void dfs(long long x,long long fa)
{
    f[x][0]=fa;
    dep[x]=dep[fa]+1;
    for(int i=1;i<=31;i++)
    {
        f[x][i]=f[f[x][i-1]][i-1];
    }
    for(int i=h[x];i;i=a[i].next)
    {
        if(a[i].to!=fa)
        {
            dfs(a[i].to,x);
        }
    }
}

然后是求解

if(dep[x]<dep[y])
{
    swap(x,y);
}   
while(dep[x] > dep[y])
{
    x = f[x][lg[dep[x]-dep[y]] - 1];
}
if(x==y)
{
    cout<<x<<endl;
    continue;
}
for(int k = lg[dep[x]] - 1; k >= 0;k--) 
{
    if(f[x][k] != f[y][k]) 
    {
        x = f[x][k], y = f[y][k];
    }
}

于是,我们得到了一个严格的 O ( log ⁡ n ) O(\log n) O(logn)算法

Luogu P1967 [NOIP2013 提高组] 货车运输

原题展现

题目描述

A 国有 n n n 座城市,编号从 1 1 1 n n n,城市之间有 m m m 条双向道路。每一条道路对车辆都有重量限制,简称限重。

现在有 q q q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

输入格式

第一行有两个用一个空格隔开的整数 $ n,m$,表示 A A A 国有 $ n$ 座城市和 m m m 条道路。

接下来 m m m 行每行三个整数 x , y , z x, y, z x,y,z,每两个整数之间用一个空格隔开,表示从 $x $ 号城市到 $ y $ 号城市有一条限重为 z z z 的道路。
注意: x ≠ y x \neq y x=y,两座城市之间可能有多条道路 。

接下来一行有一个整数 q q q,表示有 q q q 辆货车需要运货。

接下来 q q q 行,每行两个整数 x , y x,y x,y,之间用一个空格隔开,表示一辆货车需要从 x x x 城市运输货物到 y y y 城市,保证 x ≠ y x \neq y x=y

输出格式

共有 q q q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出 − 1 -1 1

样例输入 #1

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

样例输出 #1

3
-1
3

提示

对于 30 % 30\% 30% 的数据, 1 ≤ n < 1000 1 \le n < 1000 1n<1000 1 ≤ m < 10 , 000 1 \le m < 10,000 1m<10,000 1 ≤ q < 1000 1\le q< 1000 1q<1000

对于 60 % 60\% 60% 的数据, 1 ≤ n < 1000 1 \le n < 1000 1n<1000 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1m<5×104 1 ≤ q < 1000 1 \le q< 1000 1q<1000

对于 100 % 100\% 100% 的数据, 1 ≤ n < 1 0 4 1 \le n < 10^4 1n<104 1 ≤ m < 5 × 1 0 4 1 \le m < 5\times 10^4 1m<5×104,$1 \le q< 3\times 10^4 $, 0 ≤ z ≤ 1 0 5 0 \le z \le 10^5 0z105

解析

因为我们想要经过的最小边最大,那么不妨构造一个最大生成树(建议使用克鲁斯卡尔算 法),这样每条边都能尽可能大

然后问题转换为树上查询,同样利用倍增法求 x − > L C A , y − > L C A x->LCA,y->LCA x>LCA,y>LCA路径中的最小边,也是可以预处理的

不过问题不保证树联通,需要判断是否有解

克鲁斯卡尔的优势就体现出来了,我们已经处理了并查集,如果两点祖先不同就直接判断为无解

核心代码如下(码风十分奇怪)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct road
{
    ll s,t,w;
}r[200005];
struct node
{
    ll to,next,w;
}a[200005];
ll n,m,t,k,x,y,fa2[100005],h[100005],fa[100005][33],f[100005][33],dep[100005],lg[100005];
bool cmp(road x,road y)
{
    return x.w>y.w;
}
void add(int x,int y,int z)
{
    t++;
    a[t].to=y;
    a[t].w=z;
    a[t].next=h[x];
    h[x]=t;
}
int find(int x)
{
    if(fa2[x]==x)return x;
    return fa2[x]=find(fa2[x]);
}
void dfs(long long x,long long fn)
{
    fa[x][0]=fn;
    dep[x]=dep[fn]+1; 
    for(int i=1;i<=31;i++)
    {
        fa[x][i]=fa[fa[x][i-1]][i-1];
        f[x][i]=min(f[x][i-1],f[fa[x][i-1]][i-1]);//f数组表示x到fa[x][i]路径的最小值
    }
    for(int i=h[x];i;i=a[i].next)
    {
        if(a[i].to!=fn)
        {   
            f[a[i].to][0]=a[i].w;
            dfs(a[i].to,x);
        }
    }
}
int lca(int x,int y)
{
    if(dep[x]<dep[y])
    {
        swap(x,y);
    }   
    while(dep[x] > dep[y])
    {
        x = fa[x][lg[dep[x]-dep[y]] - 1];
    }
    if(x==y)
    {
        return x;
    }
    for(int k = lg[dep[x]] - 1; k >= 0;k--) 
    {
        if(fa[x][k] != fa[y][k]) 
        {
            x = fa[x][k], y = fa[y][k];
        }
    }    
    return fa[x][0];
}
int work(int x,int y)//求解x到y路径的最小值,保证y是x祖先
{
    ll ans=1e9,deph=dep[x]-dep[y];
    while(deph!=0)
    {
        ll t=lg[deph]-1;
        ans=min(ans,f[x][t]);
        x=fa[x][t];
        deph=dep[x]-dep[y];
    }
    return ans;
}
int main()
{

    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        fa2[i]=i;
        lg[i] = lg[i-1] + (1 << lg[i-1] == i);
    }
    for(int i=1;i<=m;i++)
    {
        cin>>r[i].s>>r[i].t>>r[i].w;
    }
    sort(r+1,r+m+1,cmp);//克鲁斯卡尔
    int k=n-1;
    for(int i=1;i<=m;i++)
    {
        if(k==0)break;
        if(find(r[i].s)!=find(r[i].t))
        {
            add(r[i].s,r[i].t,r[i].w);
            add(r[i].t,r[i].s,r[i].w);
            fa2[find(r[i].s)]=find(r[i].t);
            k--;
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(find(i)==i)
        {
            dfs(i,0);
        }
    }
    cin>>k;
    for(int i=1;i<=k;i++)
    {
        cin>>x>>y;
        if(find(x)!=find(y))
        {
            cout<<-1<<endl;
            continue;
        }
        int lcah=lca(x,y);
        cout<<min(work(x,lcah),work(y,lcah))<<endl;
    }
}

Duck006[DuckOI]Kill the Duck

原题展现

温馨提示

Duck非常不要脸,单推自己的题

后来发现其实有好多一样的题

  • 贪玩的小孩
  • HDU 2586 How far away?

题目描述

XCR是世界名列前茅的OIer,今天在打模拟赛。

他已经AC了前四道题,准备暴切第五题,看着这个题面,突然发现不太对…

他一看五道题的名字

X o r C o u n t    t h e    N u m b e r    o f    D a n c e    S c h e m e s R e l a x i n g    T i m e A n    E a s y    P r o b l e m K i l l    t h e    D u c k X C R A K \mathtt{\color{red}{X}\color{black}{or}}\\ \mathtt{\color{red}{C}\color{black}{ount\;the\;Number\;of\;Dance\;Schemes}}\\ \mathtt{\color{red}{R}\color{black}{elaxing\;Time }}\\ \mathtt{\color{red}{A}\color{black}{n\; Easy\;Problem}}\\ \mathtt{\color{red}{K}\color{black}{ill\;the\;Duck}}\\ \mathtt{\huge{\color{red}{XCRAK}}} XorCounttheNumberofDanceSchemesRelaxingTimeAnEasyProblemKilltheDuckXCRAK

XCR十分生气,想要杀了DengDuck

DengDuck跑到了一个有 n n n个结点, n − 1 n-1 n1条边的树上

这个树的每个边都是无向的,都有边权

XCR现在有 m m m次询问,第 i ( 1 ≤ i ≤ m ) i(1 \leq i \leq m) i(1im)次给出两个正整数 x i x_i xi y i y_i yi,含义如下

DengDuck 在点 x i ( 1 ≤ x i ≤ n ) x_i(1 \leq x_i \leq n) xi(1xin) 上,XCR在点 y i ( 1 ≤ y i ≤ n ) y_i(1 \leq y_i \leq n) yi(1yin)

对于每次询问,请问XCR离DengDuck的距离是多少?

输入格式

第一行一个整数 n n n

接下来 n − 1 n-1 n1行每行三个正整数分别表示一条边的起点,终点,边权

n + 1 n+1 n+1行一个正整数 m m m

接下来 m m m行每行两个正整数 x i x_i xi y i y_i yi

输出格式

m m m行,每行一个正整数,表示DengDuck和XCR的距离

样例输入 #1

3
1 2 3
2 3 4
2

1 2
1 3

样例输出 #1

3
7

样例输入 #2

3
1 3 10
1 2 13
5
1 1
2 2
3 1
2 1
1 3

样例输出 #2

0
0
10
13
10

样例输入 #3

14
5 7 12
7 11 15
5 14 12
14 3 17
7 1 19
14 4 14
1 12 16
1 6 16
12 9 19
9 10 10
7 2 11
4 8 10
2 13 14
17
6 11
14 14
13 11
6 10
12 6
8 7
9 9
10 11
13 10
1 4
2 12
13 4
2 7
2 1
12 2
10 11
4 7

样例输出 #3

50
0
40
61
32
48
0
79
89
57
46
63
11
30
46
79
38

提示

对于一定的数据 n , m n,m n,m的范围特殊限制
5 % 5\% 5%的数据 1 ~ 20 1~20 120
20 % 20\% 20%的数据 1 ~ 3000 1~3000 13000
另外的 5 % 5\% 5%的数据 1 ~ 3000 1~3000 13000 m = 1 m=1 m=1
所有数据 1 ~ 100000 1~100000 1100000

解析

预处理出 d i s i dis_i disi表示点 i i i到根 1 1 1的距离,答案是 d i s x + d i s y − 2 d i s l c a ( x , y ) dis_x+dis_y-2dis_{lca(x,y)} disx+disy2dislca(x,y)

非常容易证明

代码如下

#include <bits/stdc++.h>
using namespace std;
int n, k, b[1000005], x, y, z, tot, h[500005], len[500005], fa[500005][33], dep[500005], lg[500005],
    f[1000005], ans;
struct node {
    int to, next, w;
} a[1000005];
void dfs(long long x, long long fn, long long l) {
    fa[x][0] = fn;
    dep[x] = dep[fn] + 1;
    len[x] = l;
    for (int i = 1; i <= 31; i++) {
        fa[x][i] = fa[fa[x][i - 1]][i - 1];
    }
    for (int i = h[x]; i; i = a[i].next) {
        if (a[i].to != fn) {
            dfs(a[i].to, x, l + a[i].w);
        }
    }
}
int lca(int x, int y) {
    if (dep[x] < dep[y]) {
        swap(x, y);
    }
    while (dep[x] > dep[y]) {
        x = fa[x][lg[dep[x] - dep[y]] - 1];
    }
    if (x == y) {
        return x;
    }
    for (int k = lg[dep[x]] - 1; k >= 0; k--) {
        if (fa[x][k] != fa[y][k]) {
            x = fa[x][k], y = fa[y][k];
        }
    }
    return fa[x][0];
}
void add(int x, int y, int z) {
    ++tot;
    a[tot].to = y;
    a[tot].next = h[x];
    a[tot].w = z;
    h[x] = tot;
}
void answer(int x, int fn) {
    for (int i = h[x]; i; i = a[i].next) {
        if (a[i].to != fn) {
            answer(a[i].to, x);
            f[x] += f[a[i].to];
        }
    }
    ans = max(ans, f[x]);
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
    }
    for (int i = 1; i <= n - 1; i++) {
        cin >> x >> y >> z;
        add(x, y, z);
        add(y, x, z);
    }
    dfs(1, 0, 0);
    cin >> k;
    for (int i = 1; i <= k; i++) {
        cin >> x >> y;
        int t = lca(x, y);
        cout << len[x] + len[y] - 2 * len[t] << endl;
    }
}

[BJWC2010] 严格次小生成树

原题展现

题目描述

小 C 最近学了很多最小生成树的算法,Prim 算法、Kruskal 算法、消圈算法等等。正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说:如果最小生成树选择的边集是 E M E_M EM,严格次小生成树选择的边集是 E S E_S ES,那么需要满足:( v a l u e ( e ) value(e) value(e) 表示边 e e e 的权值) ∑ e ∈ E M v a l u e ( e ) < ∑ e ∈ E S v a l u e ( e ) \sum_{e \in E_M}value(e)<\sum_{e \in E_S}value(e) eEMvalue(e)<eESvalue(e)

这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

输入格式

第一行包含两个整数 N N N M M M,表示无向图的点数与边数。

接下来 M M M 行,每行 3 3 3 个数 x , y , z x,y,z x,y,z 表示,点 x x x 和点 y y y 之间有一条边,边的权值为 z z z

输出格式

包含一行,仅一个数,表示严格次小生成树的边权和。

样例输入 #1

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

样例输出 #1

11

提示

数据中无向图不保证无自环

对于 50 % 50\% 50% 的数据, N ≤ 2000 N\le 2000 N2000 M ≤ 3000 M\le 3000 M3000

对于 80 % 80\% 80% 的数据, N ≤ 5 × 1 0 4 N\le 5\times 10^4 N5×104 M ≤ 1 0 5 M\le 10^5 M105

对于 100 % 100\% 100% 的数据, N ≤ 1 0 5 N\le 10^5 N105 M ≤ 3 × 1 0 5 M\le 3\times10^5 M3×105,边权 ∈ [ 0 , 1 0 9 ] \in [0,10^9] [0,109],数据保证必定存在严格次小生成树。

解析

首先,次小生成树与最小生成树不同,但仍然只有 n − 1 n-1 n1条边

所以我们考虑加入一条未选的边,再减去一条选择的边

减去的边应该尽量小,毕竟在求次小生成树

注意如果减去边与加入边权值相同的话,就不是严格次小生成树

为此,我们保存一个严格次小边,以防万一

容易证明这样一定是次小生成树

#include <bits/stdc++.h>
using namespace std;
long long t, tt, fac[1000005], n, m, x, y, cnt, z, sum, ans = 1e18, h[1000005], fir[1000005][33],
                                                        sec[1000005][33], fa[1000005][33], dep[100005];
bool vis[1000005];
inline long long read() {
    long long ans = 0;
    char c = getchar();
    while ((c < '0' || c > '9') && (c != '-')) {
        c = getchar();
    }
    ans = c - '0';
    c = getchar();
    while (c >= '0' && c <= '9') {
        ans = (ans << 3) + (ans << 1) + c - '0';
        c = getchar();
    }
    return ans;
}
struct node {
    long long s, t, w;
} a[1000005];
struct nod {
    long long to, w, next;
} b[1000005];
void add(long long x, long long y, long long z) {
    t++;
    a[t].s = x;
    a[t].t = y;
    a[t].w = z;
}
void adde(long long x, long long y, long long z) {
    tt++;
    b[tt].to = y;
    b[tt].w = z;
    b[tt].next = h[x];
    h[x] = tt;
}
long long find(long long x) {
    if (fac[x] != x) {
        fac[x] = find(fac[x]);
    }
    return fac[x];
}
long long cmp(node x, node y) { return x.w < y.w; }
void dfs(long long x, long long y) {
    fa[x][0] = y;
    for (long long j = 1; j <= 32; j++) {
        fa[x][j] = fa[fa[x][j - 1]][j - 1];
        fir[x][j] = max(fir[x][j - 1], fir[fa[x][j - 1]][j - 1]);
        sec[x][j] = max(sec[x][j - 1], sec[fa[x][j - 1]][j - 1]);
        if (fir[x][j - 1] > fir[fa[x][j - 1]][j - 1]) {
            sec[x][j] = max(fir[fa[x][j - 1]][j - 1], sec[x][j]);
        } else if (fir[x][j - 1] < fir[fa[x][j - 1]][j - 1]) {
            sec[x][j] = max(fir[x][j - 1], sec[x][j]);
        }
    }
    for (long long i = h[x]; i; i = b[i].next) {
        if (y != b[i].to) {
            dep[b[i].to] = dep[x] + 1;
            fir[b[i].to][0] = b[i].w;
            sec[b[i].to][0] = -1e18;
            dfs(b[i].to, x);
        }
    }
}
long long lca(long long x, long long y) {
    if (dep[x] < dep[y]) {
        swap(x, y);
    }
    for (long long i = 32; i >= 0; i--) {
        if (dep[fa[x][i]] >= dep[y]) {
            x = fa[x][i];
        }
    }
    if (x == y) {
        return x;
    }
    for (long long i = 32; i >= 0 && x != y; i--) {
        if (fa[x][i] != fa[y][i]) {
            x = fa[x][i], y = fa[y][i];
        }
    }

    return fa[x][0];
}
long long query(long long s, long long t, long long w) {
    long long cnt = -1e18;
    for (long long i = 32; i >= 0; i--) {
        if (dep[fa[s][i]] >= dep[t]) {
            if (w != fir[s][i])
                cnt = max(cnt, fir[s][i]);
            else
                cnt = max(cnt, sec[s][i]);
            s = fa[t][i];
        }
    }
    return cnt;
}
int main() {
    memset(b, false, sizeof(b));
    n = read(), m = read();
    for (long long i = 1; i <= m; i++) {
        x = read(), y = read(), z = read();
        add(x, y, z);
        fac[i] = i;
    }
    sort(a + 1, a + m + 1, cmp);
    for (long long j = 1; j <= m; j++) {
        long long fa1 = find(a[j].s);
        long long fa2 = find(a[j].t);
        if (fa1 != fa2) {
            sum += a[j].w;
            adde(a[j].s, a[j].t, a[j].w);
            adde(a[j].t, a[j].s, a[j].w);
            vis[j] = 1;
            fac[fa1] = fa2;
            cnt++;
            if (cnt == n - 1) {
                break;
            }
            
        }
    }
    sec[1][0] = -1e18;
    dep[1] = 1;
    dfs(1, 0);
    ans = 1e18;
    for (long long i = 1; i <= t; i++) {
        if (!vis[i]) {
            long long st = a[i].s;
            long long ed = a[i].t;
            long long fat = lca(st, ed);
            long long l = query(st, fat, a[i].w);
            long long r = query(ed, fat, a[i].w);
            ans = min(ans, sum - max(l, r) + a[i].w);
        }
    }
    cout << ans;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LCA(最近公共祖先)是指在一棵树中,找到两个节点的最近的共同祖先节点。而Tarjan算法是一种用于求解强连通分量的算法,通常应用于有向图中。它基于深度优先搜索(DFS)的思想,通过遍历图中的节点来构建强连通分量。Tarjan算法也可以用于求解LCA问题,在有向无环图(DAG)中。 具体来说,在使用Tarjan算法求解LCA时,我们需要进行两次DFS遍历。首先,我们从根节点开始,遍历每个节点,并记录每个节点的深度(即从根节点到该节点的路径长度)。然后,我们再进行一次DFS遍历,但这次我们在遍历的过程中,同时进行LCA的查找。对于每个查询,我们将两个待查询节点放入一个查询列表中,并在遍历过程中记录每个节点的祖先节点。 在遍历的过程中,我们会遇到以下几种情况: 1. 如果当前节点已被访问过,说明已经找到了该节点的祖先节点,我们可以更新该节点及其所有后代节点的祖先节点。 2. 如果当前节点未被访问过,我们将其标记为已访问,并将其加入到查询列表中。 3. 如果当前节点有子节点,我们继续递归遍历子节点。 最终,对于每个查询,我们可以通过查询列表中的两个节点的最近公共祖先节点来求解LCA。 需要注意的是,Tarjan算法的时间复杂度为O(V+E),其中V为节点数,E为边数。因此,对于大规模的树结构,Tarjan算法是一种高效的求解LCA问题的方

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值