挑战程序设计竞赛 算法和数据结构 第15章 高等图算法

挑战程序设计竞赛 算法和数据结构 第15章 高等图算法

15.1 所有点对间最短路径

GRL_1_C:All Pairs Shortest Path

原书AC代码:

//GRL_1_C:All Pairs Shortest Path
//C++
#include <iostream>
#include <algorithm>
#include <vector>
#include <climits>
using namespace std;
static const int MAX=100;
static const long long INFTY=(1LL<<32);
int n;
long long d[MAX][MAX];
void floyd(){
    for(int k=0;k<n;k++){
        for(int i=0;i<n;i++){
            if(d[i][k]==INFTY)continue;
            for(int j=0;j<n;j++){
                if(d[k][j]==INFTY)continue;
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
}
int main(){
    int e,u,v,c;
    cin>>n>>e;
    for(int i=0;i<n;i++){
        for(int j=0;j<n;j++){
            d[i][j]=((i==j)?0:INFTY);
        }
    }
    for(int i=0;i<e;i++){//1此处写成 for(int i=0;i<n;i++)
        cin>>u>>v>>c;
        d[u][v]=c;
    }
    floyd();
    bool negative=false;
    for(int i=0;i<n;i++)if(d[i][i]<0)negative=true;
    if(negative){
        cout<<"NEGATIVE CYCLE"<<endl;
    }else{
        for(int i=0;i<n;i++){
            for(int j=0;j<n;j++){
                if(j)cout<<" ";
                if(d[i][j]==INFTY)cout<<"INF";
                else cout<<d[i][j];
            }
            cout<<endl;
        }
    }
    return 0;
}


仿照上述代码,本人编写的C语言AC代码如下:

//GRL_1_C:All Pairs Shortest Path
//样例很快通过,提交Wrong Answer,马上翻出题目,对输出字符 ,没问题
//突然想到之前编写的书中代码,用的是long long,明白应该是int 数据溢出,要改成long long
//修改,提交Wrong Answer, 修改#define INF 2147483647//2 #define INF 1999999999
//提交Wrong Answer.多次提交未果,想起了书中代码的写法,明白了要在floyd算法里进行优化 ,优化的目的,防止数据越界
//修改,提交Wrong Answer, 修改 #define INF ((long long)1<<35)//4 总长度1000*2*10^7=2*10^10提交Wrong Answer
//多次提交未果,无奈,与书中代码进行对照,找出差别。
//查了一圈下来,发现 CYCLE 写成GYCLE愣是没看出来。真是愚蠢的错误 ,提交AC 2017-10-5 21:05
//重新对代码进行修改,发现删除floyd中的两个continue语句,提交Wrong Answer,为何,思考未果。
#include <stdio.h>
#define INF ((long long)1<<35)//4 总长度1000*2*10^7=2*10^10 3#define INF 2147483647 2 #define INF 1999999999 1 #define INF 999999999
int v,e;
long long a[110][110];//1 int 改成 long long  
void floyd(){
    int i,j,k;
    for(k=0;k<v;k++)
        for(i=0;i<v;i++){
            if(a[i][k]==INF)continue;//3 floyd算法优化
            for(j=0;j<v;j++){
                if(a[k][j]==INF)continue;//3 floyd算法优化
                if(a[i][j]>a[i][k]+a[k][j])
                    a[i][j]=a[i][k]+a[k][j];
            }
        }
}
int main(){
    int i,j,s,t,d;
    scanf("%d%d",&v,&e);
    for(i=0;i<v;i++)
        for(j=0;j<v;j++)
            a[i][j]=(i==j)?0:INF;
    for(i=1;i<=e;i++){
        scanf("%d%d%d",&s,&t,&d);
        a[s][t]=d;
    }
    floyd();
    for(i=0;i<v;i++)//判断负环
        if(a[i][i]<0){
            printf("NEGATIVE CYCLE\n");//5 此处写成 printf("NEGATIVE GYCLE\n");
            return 0;
        }
    for(i=0;i<v;i++){
        for(j=0;j<v;j++){
            if(j)printf(" ");
            if(a[i][j]==INF)printf("INF");
            else printf("%lld",a[i][j]);//1 原来printf("%d",a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

收获,floyd算法判断负环,以及floy算法的优化:减少运算次数,避免数据的溢出。

15.2 拓扑排序

GRL_4_B:Topological Sort

样例数据对应的图:


原书AC代码:

//GRL_4_B:Topological Sort
//C++(广度优先搜索实现的拓扑排序)
#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <list>
using namespace std;
static const int MAX=100000;
static const int INFTY=(1<<29);
vector<int> G[MAX];
list<int> out;
bool V[MAX];
int N;
int indeg[MAX];
void bfs(int s){
    queue<int> q;
    q.push(s);
    V[s]=true;
    while(!q.empty()){
        int u=q.front();q.pop();
        out.push_back(u);
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i];
            indeg[v]--;
            if(indeg[v]==0&&!V[v]){
                V[v]=true;
                q.push(v);
            }
        }
    }
}
void tsort(){
    for(int i=0;i<N;i++){
        indeg[i]=0;
    }
    for(int u=0;u<N;u++){
        for(int i=0;i<G[u].size();i++){
            int v=G[u][i];
            indeg[v]++;
        }
    }
    for(int u=0;u<N;u++)
        if(indeg[u]==0&&!V[u])bfs(u);
    for(list<int>::iterator it=out.begin();it!=out.end();it++){
        cout<<*it<<endl;
    }
}
int main(){
    int s,t,M;
    cin>>N>>M;
    for(int i=0;i<N;i++)V[i]=false;
    for(int i=0;i<M;i++){
        cin>>s>>t;
        G[s].push_back(t);
    }
    tsort();
    return 0;
}

//GRL_4_B:Topological Sort
//C++(深度优先搜索实现的拓扑排序)
//该程序对应的样例输出数据为:
//3
//4
//5
//0
//1
//2
//当然,提交,同样能AC
//理解该算法花了很长时间,比较纠结的是,没有入度,尽然能拓扑排序,
//对样例中 点1 的打印,感觉疑惑,如何在点1之前先打印点0,点3
//仿照样例,根据自己的疑惑,自个创设了一组输入输出数据,如下:

//创设数据对应的图


//输入:
//6 6
//0 1
//0 4
//1 2
//2 5
//3 4
//4 5
//输出:
//3
//0
//4
//1
//2
//5
//基本弄明白了样例中,点1(入度为2)的打印机理
//第一次访问点1的时候,就将其标记为访问过,之后的访问中自然不会出现点1
//并且整个数据的打印顺序,是按栈的方式来打印的。
//再说一遍,该题的神奇之处是,没有入度,同样能拓扑排序,深搜的拓扑排序,该书的写法让人耳目一新。
//2017-10-8 7:38
#include <iostream>
#include <vector>
#include <algorithm>
#include <list>
using namespace std;
static const int MAX=100000;
vector<int> G[MAX];
list<int> out;
bool V[MAX];
int N;
void dfs(int u){
    V[u]=true;
    for(int i=0;i<G[u].size();i++){
        int v=G[u][i];
        if(!V[v])dfs(v);
    }
    out.push_front(u);
}
int main(){
    int s,t,M;
    cin>>N>>M;
    for(int i=0;i<N;i++)V[i]=false;
    for(int i=0;i<M;i++){
        cin>>s>>t;
        G[s].push_back(t);
    }
    for(int i=0;i<N;i++){
        if(!V[i])dfs(i);
    }
    for(list<int>::iterator it=out.begin();it!=out.end();it++){
        cout<<*it<<endl;
    }
    return 0;
}


仿照上述代码,本人编写的C语言AC代码如下:

//GRL_4_B:Topological Sort
//C(广度优先搜索实现的拓扑排序)
//邻接表,队列
//本程序,样例对应的输出数据为:
//0
//3
//4
//1
//5
//2
//原因为addedge函数,加入边长与书中C++(广度优先搜索实现的拓扑排序)代码加入边长方式不一样。
//虽然输出结果与原书略有不同,但是提交同样能AC, 2017-10-7 19:44 AC
#include <stdio.h>
#include <string.h>
struct node{
    int to,next;
}e[100100];//有向边
int n,m,head[10100],cnt=0,rd[10100],vis[10100],q[10100];//rd[]入度
void addedge(int u,int v){
    cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void bfs(int x){
    int h,t,u,v,b;//h队首,t队尾  b边
    h=t=1;
    q[t]=x;
    t++;
    while(h<t){
        u=q[h];
        vis[u]=1;
        printf("%d\n",u);
        for(b=head[u];b;b=e[b].next){
            v=e[b].to;
            if(vis[v]==0){
                rd[v]--;
                if(rd[v]==0){
                    q[t]=v;
                    t++;
                }
            }
        }
        h++;
    }
}
int main(){
    int u,v,i;
    memset(rd,0,sizeof(rd));
    memset(vis,0,sizeof(vis));
    memset(head,0,sizeof(head));
    scanf("%d%d",&n,&m);//n点数 m边数
    for(i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
        rd[v]++;
    }
    for(i=0;i<n;i++)
        if(vis[i]==0&&rd[i]==0)
            bfs(i);
    return 0;
}

体会,感觉拓扑排序代码编写得比以前更加自然。

//GRL_4_B:Topological Sort
//C(深度优先搜索实现的拓扑排序)
//邻接表,深搜
//样例对应的输出数据如下:
//3
//4
//5
//0
//1
//2
//2017-10-8 8:29 AC
#include <stdio.h>
#include <string.h>
struct node{
    int to,next;
}e[100100];
int cnt=0,head[10100],vis[10100],stack[10100],top=0;
void addedge(int u,int v){
    cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void dfs(int x){
    int b,v;
    vis[x]=1;
    for(b=head[x];b;b=e[b].next){
        v=e[b].to;
        if(vis[v]==0)
            dfs(v);
    }
    stack[++top]=x;//1 此处写成 stack[++top]=v; 查了会,真是大失水准
}
int main(){
    int n,m,i,j,u,v;
    memset(head,0,sizeof(head));
    memset(vis,0,sizeof(vis));
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
    }
    for(i=0;i<n;i++)
        if(vis[i]==0)
            dfs(i);
    while(top){
        printf("%d\n",stack[top--]);
    }
    return 0;
}

以前写的拓扑排序,真的是很别扭,光是书中的两个拓扑排序解法,真的是物超所值。

15.3 关节点

关节点Tarjan算法dfn[] low[]理解花了很长时间

http://blog.csdn.net/logo_fc/article/details/77489369 此文概念写得很不错,摘抄如下:

dfn[v]——v点的深搜编号
low[v]——是从v点出发的所有路径中,所能到达的点的dfn最小值

加上自己的理解,样例1,dfs过程中将无向图化成有向图,如下图:


样例2,dfs过程中将无向图化成有向图,如下图:


GRL_3_A:Articulation Points

原书AC代码:

//GRL_3_A:Articualtion Point
#include <iostream>
#include <vector>
#include <set>
using namespace std;
#define MAX 100000
vector<int> G[MAX];
int N;
bool visited[MAX];
int prenum[MAX],parent[MAX],lowest[MAX],timer;
void dfs(int current,int prev){
    //访问结点current之后立刻执行的处理
    prenum[current]=lowest[current]=timer;
    timer++;
    visited[current]=true;
    int next;
    for(int i=0;i<G[current].size();i++){
        next=G[current][i];
        if(!visited[next]){
            //即将通过结点current访问结点next时执行的处理
            parent[next]=current;
            dfs(next,current);
            //结点next搜索完毕之后立刻执行的处理
            lowest[current]=min(lowest[current],lowest[next]);
        }else if(next!=prev){
            //边current-->next为Back-edge时的处理
            lowest[current]=min(lowest[current],prenum[next]);
        }
    }
    //结点current搜索完毕之后立刻执行的处理
}
void art_points(){
    for(int i=0;i<N;i++)visited[i]=false;
    timer=1;
    //计算lowest
    dfs(0,-1);//0==root
    set<int> ap;
    int np=0;
    for(int i=1;i<N;i++){
        int p=parent[i];
        if(p==0)np++;
        else if(prenum[p]<=lowest[i])ap.insert(p);
    }
    if(np>1)ap.insert(0);
    for(set<int>::iterator it=ap.begin();it!=ap.end();it++)
        cout<<*it<<endl;
}
int main(){
    int m;
    cin>>N>>m;
    for(int i=0;i<m;i++){
        int s,t;
        cin>>s>>t;
        G[s].push_back(t);
        G[t].push_back(s);
    }
    art_points();
    return 0;
}


仿照上述代码,本人编写的C语言AC代码如下:

//GRL_3_A:Articualtion Point
//光靠书上这点总结性说明,理解关节点的算法,是不够的,搜索网络,找到一篇好文
//http://www.cnblogs.com/yu-chao/archive/2011/12/17/2291403.html
//http://www.cnblogs.com/en-heng/p/4002658.html此文也写得不错
//https://wenku.baidu.com/view/112c64096c85ec3a87c2c527.html?re=view此文运算过程写得真不错。
//理了思路,邻接表,dfs确定dfn[],low[],parent[],最后结果要靠快排, 
//样例通过后,提交Wrong Answer,进行1修改,提交Wrong Answer ,停滞了一天时间。 
//对照书中代码,还是找不出问题,抓狂,无奈,自个造了好多组数据,与输出程序进行对拍,找到一组好数据,如下
//输入:
//7 6
//0 1
//0 2
//1 3
//1 4
//2 5
//2 6


//输出:
//0
//1
//2

//配图如下:


//测试自身程序,与上述输出结果不同,本人输出结果如下:
//0 
//1
//1
//2
//2
//这就好办了,可以开始排错了。
//就书中代码进行研究,发现中间过程也存在如上数据:
//0 
//1
//1
//2
//2
//也就是说,该算法多次找到的关节点可能是同一个点,只是set<int> ap;ap.insert(p);有避免重复数据的功能,故书中代码最后输出的结果为:
//0
//1
//2
//问题找到了,那解决的办法就很简单了。对关节点数据用book[]进行标记,不怕重复了。连快排都不需要了。
//该题编的磕磕绊绊,很快上述例子通过,提交AC。 2017-10-11 21:26 
#include <stdio.h>
#include <string.h>
int n,m,head[100100],cnt=0,parent[100100],dfn[100100],low[100100],vis[100100],tag=0,book[100100],count=0;
struct node{
    int to,next;
}e[100000*2+100];//无向图
void addedge(int u,int v){
    cnt++,e[cnt].to=v,e[cnt].next=head[u],head[u]=cnt;
}
void dfs(int current,int previous){//设置 dfn[] low[] 
    int next,b;//next点 b边 
    vis[current]=1;
    parent[current]=previous;
    tag++;
    dfn[current]=low[current]=tag;
    for(b=head[current];b;b=e[b].next){
        next=e[b].to;
        if(vis[next]==0){ 
            dfs(next,current);
            low[current]=low[current]>low[next]?low[next]:low[current];
        }else if(next!=previous){
            low[current]=low[current]>low[next]?low[next]:low[current];//改成low[current]=low[current]>dfn[next]?dfn[next]:low[current];也能AC,本人倾向于用low[current]=low[current]>low[next]?low[next]:low[current];更容易理解.dfn[]用来标记访问顺序,low[]识别能到达最小访问顺序的点 2017-10-11 22:35 细细思考,觉得还是用low[current]=low[current]>dfn[next]?dfn[next]:low[current];比较好,当然,到底用哪个比较好,还是由读者自行决定。2017-10-12 11:05
        }
    }

void art_point(){
    int i,p,p_num=0;
    for(i=0;i<n;i++){
        p=parent[i];
        if(p==0)p_num++;
        else if(p>0&&dfn[p]<=low[i])book[p]=1;
    }
    if(p_num>1)book[0]=1;
}
int main(){
    int i,u,v;
    memset(vis,0,sizeof(vis));
    memset(head,0,sizeof(head));
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        addedge(u,v);
        addedge(v,u);
    }
    parent[0]=-1;
    dfs(0,-1);
    art_point();
    for(i=0;i<n;i++)
        if(book[i])
            printf("%d\n",i);
    return 0;


15.4 树的直径

GRL_5_A:Diameter of a Tree

原书AC代码:

//GRL_5_A:Diameter of a Tree
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
#define MAX 100000
#define INFTY (1<<30)
class Edge{
    public:
        int t,w;
        Edge(){};
        Edge(int t,int w):t(t),w(w){}
};
vector<Edge> G[MAX];
int n,d[MAX];
bool vis[MAX];
int cnt;
void bfs(int s){
    for(int i=0;i<n;i++)d[i]=INFTY;
    queue<int> Q;
    Q.push(s);
    d[s]=0;
    int u;
    while(!Q.empty()){
        u=Q.front();Q.pop();
        for(int i=0;i<G[u].size();i++){
            Edge e=G[u][i];
            if(d[e.t]==INFTY){
                d[e.t]=d[u]+e.w;
                Q.push(e.t);
            }
        }
    }
}
void solve(){
    //从任选的结点s出发,选择距离s最远的结点tgt
    bfs(0);
    int maxv=0;
    int tgt=0;
    for(int i=0;i<n;i++){
        if(d[i]==INFTY)continue;
        if(maxv<d[i]){
            maxv=d[i];
            tgt=i;
        }
    }
    //从tgt出发,求结点tgt到最远节点的距离maxv
    bfs(tgt);
    maxv=0;
    for(int i=0;i<n;i++){
        if(d[i]==INFTY)continue;
        maxv=max(maxv,d[i]);
    }
    cout<<maxv<<endl;
}
main(){
    int s,t,w;
    cin>>n;
    for(int i=0;i<n-1;i++){
        cin>>s>>t>>w;
        G[s].push_back(Edge(t,w));
        G[t].push_back(Edge(s,w));
    }
    solve();
}


仿照上述代码,本人编写的C语言AC代码如下:

//GRL_5_A:Diameter of a Tree
//请注意,因为树边的特点,vis[]数组是不需要的
//样例通过,提交Runtime Error,马上想到无向图,边设置得少了。修改,提交AC 2017-10-14 12:09
#include <stdio.h>
#include <string.h>
#define INF 999999
struct node{
    int to,next,w;
}e[100100*2];//2 此处写成 e[100100]
int cnt=0,head[100100],n,d[100100],q[100100];
void addedge(int u,int v,int w){//邻接表
    cnt++,e[cnt].to=v,e[cnt].next=head[u],e[cnt].w=w,head[u]=cnt;
}
void bfs(int x){
    int h,t,i,u,v,b;
    for(i=0;i<n;i++)d[i]=INF;
    h=t=1;
    q[t]=x;
    t++;
    d[x]=0;
    while(h<t){
        u=q[h];
        for(b=head[u];b;b=e[b].next){
            v=e[b].to;
            if(d[v]==INF){
                d[v]=d[u]+e[b].w;
                q[t]=v;
                t++;
            }
        }
        h++;
    }
}
int main(){
    int i,u,v,w,k,maxv;
    memset(head,0,sizeof(head));
    scanf("%d",&n);
    for(i=1;i<n;i++){
        scanf("%d%d%d",&u,&v,&w);
        addedge(u,v,w);//无向图
        addedge(v,u,w);
    }
    bfs(0);
    maxv=-1;
    for(i=0;i<n;i++)
        if(d[i]!=INF&&maxv<d[i]){
            maxv=d[i];
            k=i;
        }
    bfs(k);
    maxv=-1;
    for(i=0;i<n;i++)
        if(d[i]!=INF&&maxv<d[i])
            maxv=d[i];
    printf("%d\n",maxv);//1 一个想不到的错误,此处写成 printf("%d\n",d[i]); 竟然查了会
    return 0;
}


15.5 最小生成树

GRL_2_A:Minimum Spanning Tree

原书AC代码:

//GRL_2_A:Minimum Spanning Tree
//C++
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define MAX 10000
#define INFTY (1<<29)
class DisjointSet{
    //请参考DSL_1_A的参考答案
    public:
        vector<int> rank,p;
        DisjointSet(){}
        DisjointSet(int size){
            rank.resize(size,0);
            p.resize(size,0);
            for(int i=0;i<size;i++)makeSet(i);
        } 
        void makeSet(int x){
            p[x]=x;
            rank[x]=0;
        }
        bool same(int x,int y){
            return findSet(x)==findSet(y);
        }
        void unite(int x,int y){
            link(findSet(x),findSet(y));
        }
        void link(int x,int y){
            if(rank[x]>rank[y]){
                p[y]=x;
            }else{
                p[x]=y;
                if(rank[x]==rank[y]){
                    rank[y]++;
                }
            }
        }
        int findSet(int x){
            if(x!=p[x]){
                p[x]=findSet(p[x]);
            }
            return p[x];
        }
};
class Edge{
    public:
        int source,target,cost;
        Edge(int source=0,int target=0,int cost=0):
        source(source),target(target),cost(cost){}
        bool operator < (const Edge &e)const{
            return cost < e.cost;
        }
};
int kruskal(int N,vector<Edge> edges){
    int totalCost=0;
    sort(edges.begin(),edges.end());
    DisjointSet dset=DisjointSet(N+1);
    for(int i=0;i<N;i++)dset.makeSet(i);
    int source,target;
    for(int i=0;i<edges.size();i++){
        Edge e=edges[i];
        if(!dset.same(e.source,e.target)){
            //MIS.push_back(e);
            totalCost+=e.cost;
            dset.unite(e.source,e.target);
        }
    }
    return totalCost;
}
int main(){
    int N,M,cost;
    int source,target;
    cin>>N>>M;
    vector<Edge> edges;
    for(int i=0;i<M;i++){
        cin>>source>>target>>cost;
        edges.push_back(Edge(source,target,cost));
    }
    cout<<kruskal(N,edges)<<endl;
    return 0;
}

未仿照上述代码,本人独立编写的C语言AC代码如下:

参考了:

http://blog.csdn.net/mrcrack/article/details/61625530

//P3366 【模板】最小生成树

代码

//GRL_2_A:Minimum Spanning Tree
//C
//快排编写出现些小错误,修改,提交AC 2017-10-15 15:28
#include <stdio.h>
struct node{
    int u,v,w;
}e[100100],e_t;
int f[10100];
int getf(int u){
    if(f[u]==u)
        return u;
    f[u]=getf(f[u]);
    return f[u];
}
int merge(int u,int v){//左靠
    int f1,f2;
    f1=getf(u);
    f2=getf(v);
    if(f1==f2)//无需合并,同一个父亲
        return 0;
    f[f2]=f1;
    return 1;
}
void quicksort(int left,int right){//自小到大排序
    int i=left,j=right,mid=e[(left+right)/2].w;
    while(i<=j){
        while(e[i].w<mid)i++;//低级错误,此处写成 if(e[i].w<mid)i++;
        while(e[j].w>mid)j--;//低级错误,此处写成 if(e[j].w>mid)j--;
        if(i<=j){
            e_t=e[i];
            e[i]=e[j];
            e[j]=e_t;
            i++;
            j--;
        }
    }
    if(left<j)quicksort(left,j);
    if(i<right)quicksort(i,right);
}
int main(){
    int n,m,i,u,v,w,count=0,sum=0;
    scanf("%d%d",&n,&m);
    for(i=0;i<n;i++)
        f[i]=i;
    for(i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        e[i].u=u,e[i].v=v,e[i].w=w;
    }
    quicksort(1,m);
    for(i=1;i<=m;i++)
        if(merge(e[i].u,e[i].v)){
            sum+=e[i].w;
            count++;
            if(count==n-1)
                break;
        }
    printf("%d\n",sum);
    return 0;
}

 2017-10-15 15:28 AC该章节内容


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值