Link-Cut Tree动态树

把树边划分为实边和虚边,用Splay来维护若干实边连成的实链。

Link-Cut Tree支持:查询修改链上信息;换根;动态加边、删边。

性质:
  1. 每棵 S p l a y Splay Splay维护一条从上到下深度严格递增的链, S p l a y Splay Splay的中序遍历得到的深度序列递增。

  2. 每个节点包含且仅被包含在一棵 S p l a y Splay Splay

  3. 边分为实边和虚边。实边存在 S p l a y Splay Splay中,虚边由一棵 S p l a y Splay Splay指向另一个节点(由 S p l a y Splay Splay中中序遍历最靠前的点指向原树中它的父亲)

    对于拥有多个儿子的节点,只能向其中一个儿子连实边,其他的儿子不能在这棵 S p l a y Splay Splay中。虚边连向的儿子,由儿子属于的 S p l a y Splay Splay的根节点的父亲指向该节点,而从节点不能直接访问该儿子。(认父不认子)


操作:
  1. a c c e s s ( x ) access (x) access(x)

    定义:打通从根节点到节点 x x x的实链,使得中序遍历为从根节点开始到 x x x节点结束的Splay出现。

    假设现在有一棵树,实边和虚边这样划分

    原树

    所构成的LCT可能长成这样(其中绿框为一棵Splay)

    假设要 a c c e s s ( N ) access(N) access(N),把 A − N A-N AN的路径变成一棵Splay。那么需要让这条链上的节点到该路劲以外的实边变虚。

    所以希望实边虚边重新划分成这样:

    首先 s p l a y ( N ) splay (N) splay(N),使 N N N成为 S p l a y Splay Splay的根,然后将 N − O N-O NO的边变虚。

    因为Splay中 O O O N N N的右子树中,即 O O O的深度大于 N N N的深度,因此将 N N N的右儿子设为0(认父不认子)

    接着把 N N N所属 S p l a y Splay Splay的虚边指向的 I I I(在原树上是 L L L的父亲)也转到所属 S p l a y Splay Splay的根。 S p l a y ( I ) Splay(I) Splay(I)

    把原来 I − K I-K IK的实边变虚( K K K深度大于 I I I,把 I I I转到根后, K K K I I I的右儿子)

    这时再把 I − L I-L IL变重,将 I I I的右儿子设为 N N N。此时变为这样:

    I I I指向 H H H S p l a y ( H ) Splay (H) Splay(H),把 H H H的右儿子设为 I I I

    H H H指向 A A A S p l a y ( A ) Splay (A) Splay(A),把 A A A的右儿子设为 H H H

    此时 A − N A-N AN已在一棵 S p l a y Splay Splay中。

    总结流程:

    1. v v v转到根
    2. 修改右儿子
    3. 更新信息
    4. v v v设为 v v v虚边指向的父亲,继续1.
    void access (int x) {
        for (int y = 0; x; x = f[y = x]) {
            splay (x); ch[x][1] = y; pushup (x) ;
        }
    }
    
  2. m a k e r o o t ( x ) makeroot(x) makeroot(x)

    定义:让节点 x x x成为原树的根。

    a c c e s s ( x ) access(x) access(x) x x x没有右子树,即该 S p l a y Splay Splay中所有节点深度都小于 x x x。翻转整颗 S p l a y Splay Splay x x x没有了左子树,即 x x x成为了深度最小的点(根节点)

    void pushr (int x) {
        swap (ch[x][0], ch[x][1]); r[x] ^= 1 ;
    }
    void makeroot (int x) {
        access(x); splay (x); pushr (x) ;
    }
    
  3. f i n d r o o t ( x ) findroot(x) findroot(x)

    定义:寻找 x x x在原树中的树根,主要用来判断 x x x y y y之间的连通性: f i n d r o o t ( x ) = = f i n d r o o t ( y ) findroot(x)==findroot(y) findroot(x)==findroot(y)

    int findroot (int x) {
        access (x); splay (x) ;
        while (ch[x][0]) pushdown (x), x = ch[x][0] ;
        splay (x); return x ;
    }
    
  4. s p l i t ( x , y ) split (x,y) split(x,y)

    定义:将 x x x y y y的路径拉成一棵 S p l a y Splay Splay

    void split (int x, int y) {
        makeroot (x); access (y); splay (y) ;
    }
    
  5. l i n k ( x , y ) link (x, y) link(x,y)

    定义:连一条 x − y x-y xy的边

    void link (int x, int y) {
        makeroot (x) ;
        if (findroot (y) != x) f[x] = y ;
    }
    
  6. c u t ( x , y ) cut(x,y) cut(x,y)

    定义:将 x − y x-y xy的边断开

    void cut (int x, int y) {
        makeroot (x);
        if (findroot(y) == x && f[y] == x && !ch[y][0])
            f[y] = ch[x][1] = 0, pushup (x) ;
    }
    

    需要判断是否存在 x − y x-y xy的边。先判 x x x y y y的连通性( f i n d r o o t ( y ) findroot(y) findroot(y) x x x变成了根);再判断 x x x y y y是否直接连边,因为若不直接连边,中序遍历中间的节点会在 x x x y y y之间,不满足 x x x y y y直接相连;判断 y y y是否有左儿子,有代表存在深度小于 y y y的节点。


Luogu P3690【模板】Link Cut Tree (动态树)

题意:4种操作:询问 x x x y y y路径上点权的异或和;连接 x x x y y y,若已经连通则无需连接;删除 x x x y y y之间的边,不保证合法;将 x x x的权值改为 y y y

题解:LCT基本操作, S p l a y Splay Splay维护子树异或和

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 10 ;
int ch[maxn][2], f[maxn], st[maxn], s[maxn], val[maxn] ;
bool r[maxn] ;
bool nroot (int x) {
    return ch[f[x]][0] == x || ch[f[x]][1] == x ;
}
void pushup (int x) {
    s[x] = s[ch[x][0]] ^ s[ch[x][1]] ^ val[x] ;
}
void pushr (int x) {
    swap (ch[x][0], ch[x][1]); r[x] ^= 1 ;
}
void pushdown (int x) {
    if (r[x]) {
        if (ch[x][0]) pushr (ch[x][0]) ;
        if (ch[x][1]) pushr (ch[x][1]) ;
        r[x] = 0 ;
    }
}
void rotate (int x) {
    int y = f[x], z = f[y], k = ch[y][1] == x, w = ch[x][!k] ;
    if (nroot(y)) ch[z][ch[z][1] == y] = x ;
    ch[x][!k] = y; ch[y][k] = w ;
    if (w) f[w] = y ;
    f[y] = x; f[x] = z ;
    pushup (y) ;
}
void splay (int x) {
    int y = x, top = 0 ;
    st[++ top] = y ;
    while (nroot (y)) st[++ top] = y = f[y] ;
    while (top) pushdown (st[top --]) ;
    while (nroot (x)) {
        y = f[x]; int z = f[y] ;
        if (nroot (y)) rotate ((ch[y][0] == x) ^ (ch[z][0] == y) ? x : y) ;
        rotate (x) ;
    }
    pushup (x) ;
}
void access (int x) {
    for (int y = 0; x; x = f[y = x]) {
        splay (x); ch[x][1] = y; pushup (x) ;
    }
}
void makeroot (int x) {
    access(x); splay (x); pushr (x) ;
}
int findroot (int x) {
    access (x); splay (x) ;
    while (ch[x][0]) pushdown (x), x = ch[x][0] ;
    splay (x); return x ;
}
void split (int x, int y) {
    makeroot (x); access (y); splay (y) ;
}
void link (int x, int y) {
    makeroot (x) ;
    if (findroot (y) != x) f[x] = y ;
}
void cut (int x, int y) {
    makeroot (x);
    if (findroot(y) == x && f[y] == x && !ch[y][0])
        f[y] = ch[x][1] = 0, pushup (x) ;
}

int main() {
    int n, m ;
    cin >> n >> m ;
    for (int i = 1; i <= n; i ++)
        scanf("%d", &val[i]) ;
    while (m --) {
        int op, x, y ;
        scanf("%d%d%d", &op, &x, &y) ;
        if (op == 0) split (x, y), printf("%d\n", s[y]) ;
        else if (op == 1) link (x, y) ;
        else if (op == 2) cut (x, y) ;
        else splay (x), val[x] = y ;
    }
    return 0 ;
}

bozo3669 NOI2014 魔法森林

题意:给定 n n n个点 m m m条边的无向图,每条边有 a a a b b b两个权值,求一条 1 1 1 n n n的路径使得经过的边的 a a a的最大值加 b b b的最大值最小,输出这个值。

题解:

模仿kruskal先将边按 a a a排序,考虑动态向图内加边:当一条边 u − v u-v uv的两个端点 u u u v v v已经连通,查询 u u u v v v路径上 b b b的最大值 b m a x b_{max} bmax,若 b m a x b_{max} bmax大于这条边的 b b b,那么断开 b m a x b_{max} bmax的边,连接 u − v u-v uv。此时经过的 a a a的最大值就是最后加入的边的 a a a,最大的 b b b 1 1 1 n n n路径上 b b b的最大值。

综上,需要一个能够动态加边删边,维护路径最大值的数据结构,LCT即可完成。

#include <bits/stdc++.h>
using namespace std ;
inline int read() {
    int x = 0, f = 1; char c; c = getchar() ;
    while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar() ;
    return x * f ;
}
const int maxn = 2e5 + 10, maxm = 1e5 + 10 ;
struct edge {
    int u, v, a, b ;
    bool operator < (const edge &x) const {
        return a < x.a ;
    }
}e[maxm] ;
int n, m ;
int ch[maxn][2], fa[maxn], st[maxn], mx[maxn] ;
bool r[maxn] ;
int val[maxn], f[maxn] ;
int find (int x) {
    return f[x] == x ? x : f[x] = find (f[x]) ;
}
void unite (int x, int y) {
    x = find (x); y = find (y) ;
    f[x] = y ;
}
bool nroot (int x) {
    return ch[fa[x]][0] == x || ch[fa[x]][1] == x ;
}
void pushup (int x) {
    mx[x] = x ;
    if (val[mx[ch[x][0]]] > val[mx[x]]) mx[x] = mx[ch[x][0]] ;
    if (val[mx[ch[x][1]]] > val[mx[x]]) mx[x] = mx[ch[x][1]] ;
}
void pushr (int x) {
    swap (ch[x][0], ch[x][1]); r[x] ^= 1 ;
}
void pushdown (int x) {
    if (r[x]) {
        if (ch[x][0]) pushr (ch[x][0]) ;
        if (ch[x][1]) pushr (ch[x][1]) ;
        r[x] = 0 ;
    }
}
void rotate (int x) {
    int y = fa[x], z = fa[y], k = ch[y][1] == x, w = ch[x][!k] ;
    if (nroot(y)) ch[z][ch[z][1] == y] = x ;
    ch[x][!k] = y; ch[y][k] = w ;
    if (w) fa[w] = y ;
    fa[y] = x; fa[x] = z ;
    pushup (y) ;
}
void splay (int x) {
    int y = x, top = 0 ;
    st[++ top] = y ;
    while (nroot (y)) st[++ top] = y = fa[y] ;
    while (top) pushdown (st[top --]) ;
    while (nroot (x)) {
        y = fa[x]; int z = fa[y] ;
        if (nroot (y)) rotate ((ch[y][0] == x) ^ (ch[z][0] == y) ? x : y) ;
        rotate (x) ;
    }
    pushup (x) ;
}
void access (int x) {
    for (int y = 0; x; x = fa[y = x]) {
        splay (x), ch[x][1] = y, pushup (x) ;
    }
}
void makeroot (int x) {
    access (x); splay (x); pushr (x) ;
}
int findroot (int x) {
    access (x); splay (x) ;
    while (ch[x][0]) pushdown (x), x = ch[x][0] ;
    splay (x); return x ;
}
int query (int x, int y) {
    makeroot (x); access (y); splay (y) ;
    return mx[y] ;
}
void link (int x, int y) {
    makeroot (x) ;
    if (findroot (y) != x) fa[x] = y ;
}
void cut (int x, int y) {
    makeroot (x) ;
    if (findroot (y) == x && fa[y] == x && !ch[y][0])
        fa[y] = ch[x][1] = 0, pushup (x) ;
}
int main() {
    n = read(); m = read() ;
    for (int i = 1; i <= m; i ++) {
        e[i].u = read(); e[i].v = read() ;
        e[i].a = read(); e[i].b = read() ;
    }
    sort (e + 1, e + m + 1) ;
    for (int i = 1; i <= n + m; i ++) f[i] = mx[i] = i ;
    for (int i = n + 1; i <= n + m; i ++) val[i] = e[i - n].b ;
    int ans = 2e9 ;
    for (int i = 1; i <= m; i ++) {
        int u = e[i].u, v = e[i].v; bool flag = 1 ;
        if (find (u) == find (v)) {
            int w = query (u, v) ;
            if (val[w] > e[i].b)
                cut(e[w - n].u, w), cut (e[w - n].v, w) ;
            else flag = 0 ;
        } else
            unite (u, v) ;
        if (flag) link (e[i].u, i + n), link (i + n, e[i].v) ;
        if (find (1) == find (n)) ans = min (ans, e[i].a + val[query (1, n)]) ;
    }
    if (ans < 2e9) printf("%d\n", ans) ;
    else printf("-1\n") ;
    return 0 ;
}

bzoj2002 HNOI2010 弹飞绵羊

题意:有 n n n个装置,到达第 i i i个装置会被弹到第 i + k i i+k_i i+ki个装置,若不存在第 i + k i i+k_i i+ki个装置,则被弹飞。

有两种操作:从第 x x x个装置开始弹几次会被弹飞;将 k x k_x kx修改为 y y y

题解:弹跳即连边,将第 i i i个点连向第 i + k i i+k_i i+ki个点。特别的,如果 i + k i &gt; n i+k_i&gt;n i+ki>n则连向 n + 1 n+1 n+1

对于询问操作,即询问 n + 1 n+1 n+1 x x x经过的节点数减一;修改操作即删去边 i — i + k i i—i+k_i ii+ki,连接 i — i + y i—i+y ii+y(如果 i + k i &gt; n i+k_i&gt;n i+ki>n则连向 n + 1 n+1 n+1)。

用LCT维护即可,Splay维护子树size。

#include <bits/stdc++.h>
using namespace std;
inline int read() {
    int x = 0, f = 1; char c; c = getchar() ;
    while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar() ;
    return x * f ;
}
const int maxn = 2e5 + 10 ;
int n, m ;
int ch[maxn][2], fa[maxn], sz[maxn], st[maxn] ;
bool r[maxn] ;
int val[maxn] ;
bool nroot (int x) {
    return ch[fa[x]][0] == x || ch[fa[x]][1] == x ;
}
void pushup (int rt) {
    sz[rt] = sz[ch[rt][0]] + sz[ch[rt][1]] + 1 ;
}
void pushr (int rt) {
    swap (ch[rt][0], ch[rt][1]); r[rt] ^= 1 ;
}
void pushdown (int rt) {
    if (r[rt]) {
        if (ch[rt][0]) pushr (ch[rt][0]) ;
        if (ch[rt][1]) pushr (ch[rt][1]) ;
        r[rt] = 0 ;
    }
}
void rotate (int x) {
    int y = fa[x], z = fa[y], k = ch[y][1] == x, w = ch[x][!k] ;
    if (nroot (y)) ch[z][ch[z][1] == y] = x ;
    ch[x][!k] = y; ch[y][k] = w ;
    if (w) fa[w] = y ;
    fa[y] = x; fa[x] = z ;
    pushup (y) ;
}
void splay (int x) {
    int y = x, top = 0 ;
    st[++ top] = y ;
    while (nroot (y)) st[++ top] = y = fa[y] ;
    while (top) pushdown (st[top --]) ;
    while (nroot (x)) {
        y = fa[x]; int z = fa[y] ;
        if (nroot (y)) rotate ((ch[y][0] == x) ^ (ch[z][0] == y) ? x : y) ;
        rotate (x) ;
    }
    pushup (x) ;
}
void access (int x) {
    for (int y = 0; x; x = fa[y = x])
        splay (x), ch[x][1] = y, pushup (x) ;
}
void makeroot (int x) {
    access (x); splay (x); pushr (x) ;
}
int findroot (int x) {
    access (x); splay (x) ;
    while (ch[x][0]) pushdown (x), x = ch[x][0] ;
    splay (x); return x ;
}
int select (int x, int y) {
    makeroot (x); access (y); splay (y) ;
    return sz[y] ;
}
void link (int x, int y) {
    makeroot (x) ;
    if (findroot (y) != x) fa[x] = y ;
}
void cut (int x, int y) {
    makeroot (x) ;
    if (findroot (y) == x && fa[y] == x && !ch[y][0])
        fa[y] = ch[x][1] = 0, pushup (x) ;
}
int main() {
    n = read() ;
    for (int i = 1; i <= n; i ++) {
        val[i] = read() ;
        if (i + val[i] > n) link (i, n + 1) ;
        else link (i, i + val[i]) ;
    }
    m = read() ;
    while (m --) {
        int op = read(), x = read(); x ++ ;
        if (op == 1) {
            printf("%d\n", select (x, n + 1) - 1) ;
        } else {
            int y = read() ;
            if (x + val[x] > n) cut (x, n + 1) ;
            else cut (x, x + val[x]) ;
            val[x] = y ;
            if (x + val[x] > n) link (x, n + 1) ;
            else link (x, x + val[x]) ;
        }
    }
    return 0 ;
}

bzoj4573 ZJOI2016 大森林

题意:有 n n n棵树,初始各有1个编号为1的节点,且其为生长节点。3种操作:将 [ l , r ] [l,r] [l,r]区间内的树增加一个新编号的节点(没有该节点的树忽略这次操作);修改 [ l , r ] [l,r] [l,r]区间内树的生长节点;询问某一棵树上两点间的距离。

题解:首先,无论什么操作都不会改变树的形态,询问的答案不会改变,因此考虑离线,只要从 1 1 1 n n n考虑相邻两棵树的差异并统计答案即可。

  1. 增加节点操作

    不难发现,把对 [ l , r ] [l,r] [l,r]增加节点改为对 [ 1 , n ] [1,n] [1,n]增加节点对答案没有影响。注意题目中修改生长节点时不能修改没有该节点的树,所以记录一下每个点存在的树的区间,修改时左端点取 m a x max max,右端点取 m i n min min

  2. 询问操作

    使用 a c c e s s access access L C A LCA LCA:假设在 L C T LCT LCT中求 x x x y y y L C A LCA LCA,那么先 a c c e s s ( x ) access(x) access(x) x x x到根的边变成了实边,再 a c c e s s ( y ) access(y) access(y),发现当 a c c e s s access access操作做到 L C A LCA LCA位置时,上面的边已都成实边,下一步 a c c e s s access access操作中的 x x x就变为0,这时最后操作的节点 y y y就是它们的 L C A LCA LCA

    求出 L C A LCA LCA后,只要 D e p x + D e p y − D e p L C A Dep_x+Dep_y-Dep_{LCA} Depx+DepyDepLCA即可

    对于 D e p Dep Dep,Splay记录子树内的实点(为了处理修改需要引入虚点)个数, a c c e s s access access操作后自然变成深度。

  3. 修改生长节点操作

    考虑修改生长节点后,在下一次修改生长节点操作前,在所有被修改生长节点的树中新增的节点的父亲,从原来的生长节点变为新的生长节点。我们每次修改时,就相当于要将这一坨节点从一个节点的儿子被移作另一个节点的儿子。为了快速移动,建立虚点,将这些节点作为虚点的儿子,再将虚点作为目标节点的儿子。修改时对虚点做 c u t cut cut l i n k link link即可。假设修改操作是 ( l , r , x ) (l,r,x) (l,r,x),对于第 l l l棵树可以看成第 l − 1 l-1 l1棵树把新生节点接在 x x x上,对于第 r + 1 r+1 r+1棵树可以看成第 r r r棵树把这些节点又接了回来。

因此把修改生长节点操作拆成两个操作,对所有的操作和询问按照修改位置排序(增加节点的位置是 1 1 1号节点),相同的位置先修改,再询问。查询用 a c c e s s access access操作。用LCT维护即可。

#include <bits/stdc++.h>
using namespace std;
inline int read() {
    int x = 0, f = 1; char c; c = getchar() ;
    while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar() ;
    return x * f ;
}
const int maxn = 4e5 + 10 ;
int n, m, last, cnt, ind, qn, tot ;
int ch[maxn][2], fa[maxn], val[maxn], sum[maxn] ;
int L[maxn], R[maxn], id[maxn], ans[maxn] ;
struct query {
    int pos, id, x, y ;
    query () {}
    query (int a, int b, int c, int d) {pos = a, id = b, x = c, y = d;}
    bool operator < (const query &a) const {
        if (pos == a.pos) return id < a.id ;
        return pos < a.pos ;
    }
}q[maxn] ;
void newnode (int x) {
    cnt ++; val[cnt] = sum[cnt] = x ;
}
bool nroot (int x) {
    return ch[fa[x]][0] == x || ch[fa[x]][1] == x ;
}
void pushup (int x) {
    sum[x] = sum[ch[x][0]] + sum[ch[x][1]] + val[x] ;
}
void rotate (int x) {
    int y = fa[x], z = fa[y], k = ch[y][1] == x, w = ch[x][!k] ;
    if (nroot (y)) ch[z][ch[z][1] == y] = x ;
    ch[x][!k] = y; ch[y][k] = w ;
    if (w) fa[w] = y ;
    fa[y] = x; fa[x] = z ;
    pushup (y) ;
}
void splay (int x) {
    while (nroot (x)) {
        int y = fa[x], z = fa[y] ;
        if (nroot (y)) rotate ((ch[y][1] == x) ^ (ch[z][1] == y) ? x : y) ;
        rotate (x);
    }
    pushup (x) ;
}
int access (int x) {
    int y = 0 ;
    for (; x; x = fa[y = x])
        splay (x), ch[x][1] = y, pushup (x) ;
    return y ;
}
void link (int x, int y) {
    splay (x); fa[x] = y ;
}
void cut (int x) {
    access (x); splay (x) ;
    fa[ch[x][0]] = 0; ch[x][0] = 0; pushup (x) ;
}
int ask (int x, int y) {
    int ans = 0, lca ;
    access (x), splay (x), ans += sum[x] ;
    lca = access (y), splay (y), ans += sum[y] ;
    access (lca), splay (lca), ans -= 2 * sum[lca] ;
    return ans ;
}
int main() {
    n = read(); m = read() ;
    newnode (1); L[1] = 1; R[1] = n; id[ind = 1] = 1 ;
    newnode (0); link (2, 1); last = 2 ;
    for (int i = 1; i <= m; i ++) {
        int op = read() ;
        if (op == 0) {
            int l = read(), r = read() ;
            newnode (1); L[++ ind] = l; R[ind] = r; id[ind] = cnt ;
            q[++ tot] = query (1, i - m, cnt, last) ;
        } else if (op == 1) {
            int l = read(), r = read(), x = read() ;
            l = max (l, L[x]); r = min (r, R[x]) ;
            if (l > r) continue ;
            newnode (0); link (cnt, last) ;
            q[++ tot] = query (l, i - m, cnt, id[x]) ;
            q[++ tot] = query (r + 1, i - m, cnt, last) ;
            last = cnt ;
        } else {
            int x = read(), u = read(), v = read() ;
            q[++ tot] = query (x, ++ qn, id[u], id[v]) ;
        }
    }
    sort (q + 1, q + tot + 1) ;
    for (int i = 1, j = 1; i <= n; i ++)
        for (; i == q[j].pos; j ++) {
            if (q[j].id <= 0) cut (q[j].x), link (q[j].x, q[j].y) ;
            else ans[q[j].id] = ask (q[j].x, q[j].y) ;
        }
    for (int i = 1; i <= qn; i ++) printf("%d\n", ans[i]) ;
    return 0 ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值