ACwing 第三章 搜索与图论

一、DFS

在这里插入图片描述
DFS中有两个重要的概念:回溯和剪枝
当图中所有边的权重为1时,BFS搜到的一定是最短路
在这里插入图片描述
回溯时一定要注意恢复现场
排列数字

#include<iostream>
using namespace std;
const int N = 10;

int n;
int path[N];//记录所有的搜索路径
bool st[N];//记录这些点有没有被用过,1表示是,0表示否

void dfs(int u) // 第u层
{
    if(u == n)//从0开始作为第一层,当搜索完最后一层,就输出这条路径并结束递归
    {
        for(int i=0;i<n;i++)
        {
            printf("%d ",path[i]);
        }
        puts("");
        return;
    }
    for(int i=1;i<=n;i++)
    {
        if(!st[i])
        {
            path[u] = i;//写入路径记录
            st[i] = true;//更新状态为已使用
            dfs(u+1);//给下一层找数
            //----------------------------------下一层递归结束,此时该恢复状态了
            st[i] = false;//更新状态为未使用
            path[u] = 0;//清空该层路径记录
        }
    }
}

int main()
{
    cin>>n;
    dfs(0);
    return 0;
}

n皇后问题

剪枝:提前判断,当前方案一定不合法时,没有必要往下搜,直接回溯

图中一共有16条正对角线,16条反对角线
正对角线:斜率为-1的且经过该点的直线
反对角线:斜率为1的且经过该点的直线

#include<iostream>
using namespace std;
const int N = 10;
int n;
char g[N][N];//记录方案
bool col[N],dj[2*N],fdj[2*N];//同一行(列)、正对角线、反对角线

void dfs(int u)
{
    if(u == n)//搜完最后一行,叶结点时,输出方案
    {
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<n;j++)
            {
                printf("%c",g[i][j]);
            }
            puts("");
        }
        puts("");
        return;
    }
    for(int i = 0;i < n;i++)//从第一列 x开始枚举 i:x u:y
    {
        if(!col[i] && !dj[i+u] && !fdj[n + u - i])//防止反对角为负数,加n到正常范围
        {
            g[u][i] = 'Q';//放置皇后
            col[i] = dj[i+u] = fdj[u-i+n] = true;//状态更新
            dfs(u+1);//搜索下一列
            //恢复状态,为回溯做准备
            g[u][i] = '.';
            col[i] = dj[i+u] = fdj[u-i+n] = false;
        }
    }
}

int main()
{
    cin>>n;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            g[i][j] = '.';//初始化
        }
    }
    dfs(0);
    return 0;
}

二、BFS

走迷宫

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int M = 105;
typedef pair<int,int> PII;
queue<PII>q;
int n,m;
int g[M][M];
int cnt[M][M];//存储走到每个点时经过的步数
int dx[4] = {0,0,-1,1};//x方向
int dy[4] = {1,-1,0,0};//y方向:上下左右
int bfs()
{
    q.push({0,0});
    cnt[0][0] = 0;
    while(!q.empty())
    {
        PII pos = q.front();
        q.pop();
        for(int i=0;i<4;i++)
        {
            int y = pos.first + dy[i];
            int x = pos.second + dx[i];
            if(x>=0 && x< m && y>=0 && y<n && g[y][x] == 0 && cnt[y][x] == -1) //范围内,不是墙,且没走过
            {
                q.push({y,x});
                cnt[y][x] = cnt[pos.first][pos.second] + 1; //该点的步数等于上一点的步数加1
            }
        }
    }
    return cnt[n-1][m-1];//返回出口的点的步数
}

int main()
{
    cin>>n>>m;//对于边权重全为1的图,bfs可以找到最短路,但是不能走回头路(一个点走两次)
    memset(cnt,-1,sizeof cnt);//初始化cnt数组为-1,表示没走过该点
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            cin>>g[i][j];
        }
    }
    cout<<bfs()<<endl;
    return 0;
}

输出路径

 if(x>=0 && x< m && y>=0 && y<n && g[y][x] == 0 && cnt[y][x] == -1) //范围内,不是墙,且没走过
{
q.push({y,x});
cnt[y][x] = cnt[pos.first][pos.second] + 1; //该点的步数等于上一点的步数加1
Prev[y][x] = pos;
}
PII Prev[M][M];
while(i || j)
{
        cout<<i<<" "<<j<<endl;
        pos = Prev[i][j];
        i = pos.first;
        j = pos.second;
}

三、树与图的存储

在这里插入图片描述
边多用矩阵,点多用表
邻接表一般在插入时选择头插法

数组模拟链表

在这里插入图片描述
e[]相当于val,ne[]相当于next

四、树与图的优先遍历

树的重心
在这里插入图片描述

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

const int N = 1e5+10;

int h[N],e[2*N],ne[2*N],idx;
bool st[N];
int n;
int ans = N;//答案:最大子树的最小值
void add(int a,int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}

int dfs(int u)
{
    st[u] = true;
    
    int sum = 1;//当前子树u的大小
    int res = 0;//删除该点后,每一个连通块中的点的数量的最大值
    for(int i = h[u];i!=-1;i = ne[i])
    {
        int j = e[i];
        if(!st[j])
        {
            int s = dfs(j);//当前这一棵子树j的大小
            res = max(res,s);
            sum += s;//子树u的大小等于组成其的各子树之和
        }
    }
    res = max(res,n-sum);//和父结点所在的子树比较
    ans = min(ans,res);//和答案比较,更新答案
    return sum;//返回的是子树的大小
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n;
    for(int i=0;i<n;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a);//边是双向的
    }
    dfs(1);
    cout<<ans<<endl;
}

BFS图中点的层次
经典BFS

#include<iostream>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
const int M = 1e5+10;
int h[M],e[M],ne[M],idx;
int n,m;
int d[M];//记录该点与起点的距离
queue<int>q;
void add(int a,int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx;
    idx++;
}

int bfs()
{
    q.push(1);
    memset(d,-1,sizeof d);
    d[1] = 0;//起点距离为0
    while(!q.empty())
    {
        int t = q.front();
        q.pop();
        for(int i = h[t];i != -1;i = ne[i])
        {
            int j = e[i];
            if(d[j] == -1)//距离为-1说明之前没有遍历过
            {
                q.push(j);
                d[j] = d[t] + 1;//更新距离,相邻点的距离就加1
            }
        }
    }
    return d[n];
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);//注意:这里一定要初始化链表头为-1,否则TLE
    for(int i=0;i<m;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);//单向边:a到b
    }
    cout<<bfs()<<endl;
    return 0;
}

五、拓扑序列

有向无环图一定存在拓扑序列,也被称为拓扑图
入度:有多少条边指向该结点
出度:该结点有几个指向的结点(几条边)
在这里插入图片描述
如果有环,环中的所有点不会入队,因为找不到突破口
848. 有向图的拓扑序列
注意:此题答案不唯一,输出合法拓扑序即可

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int M = 1e5 + 10;
int e[M],ne[M],h[M],idx;
int d[M],q[M],hh=0,tt=-1;//d[]是每个点的入度,注意:数组模拟队列,hh为0
int n,m,x,y;

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

bool TopSort()//bfs思路
{
    for(int i=1;i<=n;i++)//先把图中所有度为0的点入队
    {
        if(!d[i]) q[++tt] = i;
    }
    while(tt >= hh)//队列不空
    {
        int t = q[hh++];//出队
        for(int i=h[t];i!=-1;i = ne[i])//遍历该点所有指向的点
        {
            int tmp = e[i];
            d[tmp]--;//删掉指向该点的一条边,入度减一
            if(!d[tmp]) q[++tt] = tmp;//若入度为0了,说明拓扑序轮到它了,入队
        }
    }
    if(tt == n-1) return true;//如果所有点都入队了,说明是无环图
    else return false;
}

int main()
{
    memset(h,-1,sizeof h);//一定要初始化每个头结点为空
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y;
        add(x,y);
    }
    if(TopSort())
    {
        for(int i=0;i<n;i++)//出队时只是头指针移动,实际数组元素不变,队列序就是拓扑序
        {
            cout<<q[i]<<" ";
        }
        puts("");
    }
    else puts("-1");
    return 0;
}

六、最短路算法

源点:起点
汇点:终点
单源最短路:只有一个起点
多源汇最短路:多个起点
在这里插入图片描述

1.朴素版dijkstra算法

第一步:
初始化距离 dist[1] = 0,dist[i] = 正无穷
第二步:
for i :[1,n)
t <—找出不在集合S中的且距离最近的点
s<—t 把t加到s里去
用t更新其他点的距离
在这里插入图片描述
Dijkstra求最短路 I

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 505;
const int INF = 0x3f3f3f3f;//最大int值,无穷远
int g[N][N]; //邻接矩阵存储稠密图
int dist[N]; //1号点到n号点的距离
int n,m,x,y,z;
bool st[N];//是否确定了最短距离
int Dijkstra()
{
    dist[1] = 0;//起点距离为0
    for(int i=1;i<=n;i++)//n个点,循环n次
    {
        int t = -1;//存储当前未确定最短距离,且距离最小的点
        for(int j = 1;j <= n;j++)//o(n)寻找距离最小的点
        {
            if(!st[j] && (t==-1 || dist[j] < dist[t]))
            {
                t = j;
            }
        }
        
        st[t] = true;//标记已确定最短距离
        
        for(int j=1;j<=n;j++)//用该点更新所有点的最短距离
        {
            dist[j] = min(dist[j],dist[t] + g[t][j]);
        }
    }
    if(dist[n] == INF) return -1;//无穷远说明没有通路
    else return dist[n];
}

int main()
{
    cin>>n>>m;
    memset(g,INF,sizeof g);//初始化图中所有路为无穷远
    memset(dist,INF,sizeof dist);//初始化所有距离为无穷远
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        g[x][y] = min(g[x][y],z);//图中存在重边和自环,取最短的路即可解决
    }
    cout<<Dijkstra()<<endl;
}

ACM中常用的无穷大常量——0x3f3f3f3f

2.堆优化版的dijkstra算法

#include<iostream>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
using namespace std;
const int M = 1.5*1e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int,int>PII;//first表示与起点距离,second表示几号点

int h[M],e[M],ne[M],w[M],idx;//稀疏图使用邻接表来存储,w[]表示边的权重
int n,m,x,y,z;
int dist[M];
bool st[M];

void add(int a,int b,int c)
{
    e[idx] = b;
    w[idx] = c;//维护边的权重
    ne[idx] = h[a];
    h[a] = idx;
    idx++;
}

int Dijkstra()//堆优化版Dijkstra算法,有点像bfs
{
    priority_queue<PII,vector<PII>,greater<PII>>heap;//定义一个小根堆
    heap.push({0,1});//首先1号点距离为0,入堆
    dist[1] = 0;//同上
    while(!heap.empty())
    {
        PII t = heap.top();
        heap.pop();
        
        int num = t.second,distance = t.first;
        if(st[num]) continue;//如果已确定最短距离,跳过
        st[num] = true;//注意:维护已确定最短距离的状态的顺序
        for(int i = h[num];i!=-1;i=ne[i])//用已确定的距离最短的点来维护相邻点的最短距离
        {
            int num1 = e[i];
            if(dist[num1] > distance + w[i])
            {
                dist[num1] = distance + w[i];
                heap.push({dist[num1],num1});
            }
        }
    }
    if(dist[n] == INF) return -1;//没有通路
    else return dist[n];
}
int main()
{
    memset(h,-1,sizeof h);
    memset(dist,INF,sizeof dist);//初始化链表为空,最短距离为无穷远
    
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        add(x,y,z);
    }
    cout<<Dijkstra()<<endl;
    return 0;
}

3.Bellman-Ford算法(处理负边权)

![在这里插入图片描述](https://img-blog.csdnimg.cn/dd68fdc0e46c404980227f31e18df2d0.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5a6J5rKz5qGl5YyXaQ==,size_20,color_FFFFFF,t_70,g_se,x_16
有解的图中可能不存在负权回路,该算法能找出负权回路
有边数限制的最短路

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

const int N = 505,M = 10005,INF = 0x3f3f3f3f;
int n,m,k,x,y,z;
int dist[N],backup[N];//backup用于记录上一次的dist,防止在一次i循环内更新多个点的距离
struct edge
{
    int x;
    int y;
    int z;
}Edge[M];

int Bellman_Ford()
{
    memset(dist,INF,sizeof dist);//首先更新每个点的dist为无穷远
    dist[1] = 0;//源点距离为0
    for(int i=0;i<k;i++)//题目要求最多走k条边
    {
        memcpy(backup,dist,sizeof dist);//将上一次i循环后的dist复制给backup
        for(int j=0;j<m;j++)
        {
            int a = Edge[j].x;
            int b = Edge[j].y;
            int w = Edge[j].z;
            dist[b] = min(dist[b],backup[a] + w);
        }
    }
    if(dist[n] >= INF/2) return -2;//由于有负边,当不满足题意时,终点距离不一定为无穷远
    //但一定为一个大数
    else return dist[n];
}
int main()
{
    cin>>n>>m>>k;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        Edge[i] = {x,y,z};
    }
    int t = Bellman_Ford();
    if(t == -2) puts("impossible");
    else cout<<t<<endl;
    return 0;
}

4.SPFA算法(用的比较多)

spfa求最短路

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int M = 1e5+10,INF = 0x3f3f3f3f;

int h[M],e[M],w[M],ne[M],idx;
int dist[M];
bool st[M];//表示该点是否处于队列中,1为是,0为否
int n,m,x,y,z;
void add(int a,int b,int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx;
    idx++;
}

int spfa()//思想类似dijkstra算法,不同之处在于st的含义
{
    memset(dist,INF,sizeof dist);
    dist[1] = 0;
    queue<int>q;
    q.push(1);
    st[1] = true;
    while(!q.empty())
    {
        int t = q.front();
        q.pop();
        
        st[t] = false;
        
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];//若发生距离更新
                if(!st[j])//且该点不在队列中
                {
                    q.push(j);//入队并更新st[j]的状态
                    st[j] = true;
                }
            }
        }
    }
    if(dist[n] == INF) return -2;//返回-1会wa掉,可能有负边
    else return dist[n];
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        add(x,y,z);
    }
    int t = spfa();
    if(t==-2) puts("impossible");
    else cout<<t<<endl;
    return 0;
}

spfa判断负环

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int M = 1e5+10,INF = 0x3f3f3f3f;

int h[M],e[M],w[M],ne[M],idx;
int dist[M],cnt[M];//记录从1号点到n号点路径中边的个数
bool st[M];//表示该点是否处于队列中,1为是,0为否
int n,m,x,y,z;
void add(int a,int b,int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx;
    idx++;
}

bool spfa()//spfa算法求是否存在负环,思路来源于抽屉法
{
    memset(dist,INF,sizeof dist);
    //dist[1] = 0;
    queue<int>q;
    for(int i=1;i<=n;i++)//因为从1号点可能到不了n号点,因此起初必须把每个点都入队
    {
        q.push(i);
        st[i] = true;
    }
    while(!q.empty())
    {
        int t = q.front();
        q.pop();

        st[t] = false;

        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
                dist[j] = dist[t] + w[i];//若发生距离更新,且该点不在队列中
                cnt[j] = cnt[t] + 1;//相邻点的个数相差1
                if(cnt[j] >= n) return true;//若该路径上的边个数大于等于所有点的个数,则一定存在负环
                if(!st[j])
                {
                    q.push(j);//入队并更新st[j]的状态
                    st[j] = true;
                }
            }
        }
    }
    return false;//没有负环
}
int main()
{
    memset(h,-1,sizeof h);
    cin>>n>>m;
    for(int i=0;i<m;i++)
    {
        cin>>x>>y>>z;
        add(x,y,z);
    }
    if(spfa()) puts("Yes");
    else puts("No");
    return 0;
}

Floyd算法(O^3)

在这里插入图片描述
Floyd求最短路

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 205,INF = 0x3f3f3f3f;
int n,m,x,y,z,k;
int d[N][N];
void Floyd() //动态规划
{
    for(int k=1;k<=n;k++) //k必须在外循环,i,j循环次序可变
    {
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=n;j++)
            {
                d[i][j] = min(d[i][j],d[i][k] + d[k][j]);
            }
        }
    }
}
int main()
{
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++)//初始化距离数组
    {
        for(int j=1;j<=n;j++)
        {
            if(i == j) d[i][j] = 0;//相同点距离为0 
            else d[i][j] = INF;//否则设为无穷远
        }
    }
    for(int i=1;i<=m;i++)
    {
        cin>>x>>y>>z;
        d[x][y] = min(d[x][y],z);//若存在多个边,则取最小距离的边为准
    }
    Floyd();
    while(k--)
    {
        cin>>x>>y;
        if(d[x][y] >= INF/2) puts("impossible");//存在负边,所以不通的路径距离不一定为无穷大
        else cout<<d[x][y]<<endl;
    }
    return 0;
}

七、最小生成树

在这里插入图片描述
在这里插入图片描述

朴素版Prim算法(O^2)

在这里插入图片描述
858. Prim算法求最小生成树

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 505,INF = 0x3f3f3f3f;
int n,m;
int g[N][N];
int dist[N];//表示点到集合中某点的距离
bool st[N];

int prim()
{
    int res = 0;
    memset(dist,INF,sizeof dist);
    for(int i=0;i<n;i++)
    {
        int t = -1;
        for(int j=1;j<=n;j++)
        {
            if(!st[j] && (t == -1 || dist[j] < dist[t])) //j在集合外,且未找到任何一个点或j距离较小
            {
                t = j;
            }
        }
        if(i && dist[t] == INF) return INF; //不是第一个点且当前距离最近的点的距离都是无穷,
        //说明图不连通
        if(i) res += dist[t];//只要不是第一条边,就把t的距离加入到答案去
        
        for(int j=1;j<=n;j++)
        {
            dist[j] = min(dist[j],g[t][j]);
        }
        st[t] = true; //标志着当前点加入到集合中
    }
    return res;
}

int main()
{
    cin>>n>>m;
    memset(g,INF,sizeof g);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        g[a][b] = g[b][a] = min(c,g[a][b]);//无向图
    }
    int t = prim();
    if(t == INF) puts("impossible");
    else cout<<t<<endl;
    return 0;
}

Kruskal算法(快排+并查集)

在这里插入图片描述
859. Kruskal算法求最小生成树

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int M = 2e5+20,INF = 0x3f3f3f3f;
int p[M];
int n,m,a,b,c;
struct Edge
{
    int a;
    int b;
    int w;
}edges[M];

int find(int a) //查找a点的集合的祖宗
{
    if(a!=p[a]) p[a] = find(p[a]);
    return p[a];
}
bool cmp(struct Edge a,struct Edge b)
{
    return a.w < b.w;
}

void kruskal()
{
    int cnt = 0,res = 0;
    sort(edges+1,edges+m+1,cmp);//对每个边进行升序排序
    for(int i=1;i<=n;i++) //初始化所有集合
    {
        p[i] = i;
    }
    for(int i=1;i<=m;i++)
    {
        int a = edges[i].a;
        int b = edges[i].b;
        int w = edges[i].w;
        a = find(a),b = find(b); //若不在一个集合,则合并
        if(a!=b)
        {
            p[b] = a;
            cnt++;
            res += w;
        }
    }
    
    if(cnt < n-1) puts("impossible");//合并次数小于n-1,说明图不连通
    else cout<<res<<endl;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edges[i] = {a,b,c};
    }
    
    kruskal();

    return 0;
}

染色法判定二分图

二分图又称作二部图,是图论中的一种特殊模型。 设G=(V,E)是一个无向图,如果顶点V可分割为两个互不相交的子集(A,B),并且图中的每条边(i,j)所关联的两个顶点i和j分别属于这两个不同的顶点集(i in A,j in B),则称图G为一个二分图。
二分图当且仅当图中不含奇数环
染色法判定二分图

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10; // 无向图, 所以最大边数是2倍
int e[M], ne[M], h[N], idx;
int st[N];

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

bool dfs(int u, int color) {
    st[u] = color;

    for(int i = h[u]; i != -1; i = ne[i]){
        int j = e[i];
        if(!st[j]) {
            if(!dfs(j, 3 - color)) return false;
        }else if(st[j] == color) return false;
    }

    return true;
}

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

    memset(h, -1, sizeof h);
    while (m --){
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b,a);  // 无向图
    }

    bool flag = true;
    for(int i = 1; i <= n; i ++){
        if(!st[i]){
            if(!dfs(i, 1)){
                flag = false;
                break;
            }
        }
    }

    if(flag) puts("Yes");
    else puts("No");
    return 0;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值