第3讲 搜索与图论

本文深入探讨了搜索和图论在计算机科学中的应用,包括n皇后问题、走迷宫、八数码难题、树的重心计算、图的层次结构、最短路径算法如SPFA、Dijkstra和Floyd,以及最小生成树的Prim和Kruskal算法。同时介绍了染色法在判定二分图及最大匹配中的应用。
摘要由CSDN通过智能技术生成

排列数字

在这里插入图片描述

在这里插入图片描述

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

int path[N];
bool st[N];
int n;

// 计算u->n的所经过的路径path
void dfs(int u)
{
    // 边界条件
    if (u == n)     //这就是走到头了
    {
        for (int i = 0; i < n; i++) printf("%d ", path[i]);     //直接输出路径
        puts("");           //换行
        return;     //返回空值
    }
    else        //如果没有走到头
    {
        for (int i = 1; i <= n; i++)        //枚举1~n
        {
            if (!st[i])             //找到那些数字没有用过
            {
                path[u] = i;        //此时u这个节点对应的值为i
                st[i] = true;       //同时标记st[i]这个点已经访问过了
                dfs(u+1);       //向下递归,所以u+1
                //剩下这里表示,回溯的状态,回来时需要恢复现场
                path[u] = 0;        //设置u这个节点对应的值为0
                st[i] = false;          //同时标记st[i]这个点没有访问过
            }
        }
    }
}

int main()
{
    scanf("%d", &n);        //输入n
    dfs(0);         //从0节点出发
    return 0;
}


n皇后

在这里插入图片描述



#include<iostream>
using namespace std;

const int maxn=20;

int col[maxn],gcd[maxn],ugcd[maxn];

char g[maxn][maxn];

int n;

void dfs(int u)
{
    if(u==n)
    {
        for(int i=0; i<n; i++)
             puts(g[i]);         //puts直接输出g[i],并且自动输出换行
        cout << endl;
        return ;
    }

        for(int i=0; i<n; i++)
        {
            if(!col[i] && !gcd[u+i] && !ugcd[i-u+n])
            {
                g[u][i]='Q';
                col[i]=gcd[u+i]=ugcd[i-u+n]=true;
                dfs(u+1);
                col[i]=gcd[u+i]=ugcd[i-u+n]=false;
                g[u][i]='.';

            }
        }

}

int main()
{
    cin >> n;       //读入n

    for(int i=0; i<n; i++)                    //先对g初始化为 .
        for(int j=0; j<n; j++)
            g[i][j]='.';

    dfs(0);

    return 0;

}


走迷宫


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

using namespace std;

typedef pair<int, int> PII;         //用pair来存放每个点的坐标

const int N = 110;

int n, m;
int g[N][N], d[N][N];         //g存放的是每个图,d存放的是(0,0)点到每个点的距离

int bfs()           
{
    queue<PII> q;          

    memset(d, -1, sizeof d);            //所有的距离初始化为-1,表示该点没有走过
    d[0][0] = 0;                                             //初始化(0,0)这个点表示已经走过了
    q.push({0, 0});         //将(0,0)放到队列q中

    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};           //上下左右4个方向

    while (q.size())
    {
        auto t = q.front();         //访问队头元素
        q.pop();

        for (int i = 0; i < 4; i ++ )
        {
            int x = t.first + dx[i], y = t.second + dy[i];

            if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1)        //g[x][y]==0,表示空地,d[x][y]表示-1,表示该点之前没有走过
            {
                d[x][y] = d[t.first][t.second] + 1;
                q.push({x, y});
            }
        }
    }

    return d[n - 1][m - 1];
}

int main()
{
    cin >> n >> m;          //输入n行,m列
    
    for (int i = 0; i < n; i ++ )           //输入n行m列
        for (int j = 0; j < m; j ++ )
            cin >> g[i][j];        //输入其地图

    cout << bfs() << endl;          //直接输出从左上角到右下角的最少移动次数

    return 0;
}


八数码

#include <iostream>
#include <algorithm>
#include <queue>
#include <unordered_map>

using namespace std;

int bfs(string start)
{
    //定义目标状态
    string end = "12345678x";
    //定义队列和dist数组
    queue<string> q;                //q表示对列的一个状态
    unordered_map<string, int> d;           //d表示前面的装态及对应后面的 距离
    //初始化队列和dist数组
    q.push(start);              //先将start放入到q中
    d[start] = 0;
    //转移方式
    int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};           //4个方向

    while(q.size())
    {
        auto t = q.front();             //t表示q.front()
        q.pop();
        //记录当前状态的距离,如果是最终状态则返回距离
        int distance = d[t];
        if(t == end) return distance;
        //查询x在字符串中的下标,然后转换为在矩阵中的坐标
        int k = t.find('x');                    //在字符串中找到x所处在的位置
        
        int x = k / 3, y = k % 3;           //找到x对应的,第几行第几列

        for(int i = 0; i < 4; i++)
        {
            //求转移后x的坐标
            int a = x + dx[i], b = y + dy[i];           //a,b是新的下标
            //当前坐标没有越界
            if(a >= 0 && a < 3 && b >= 0 && b < 3)
            {
                //转移x
                swap(t[k], t[a * 3 + b]);           //k是当前的下标,
                //如果当前状态是第一次遍历,记录距离,入队
                if(!d.count(t))
                {
                    d[t] = distance + 1;
                    q.push(t);
                }
                //还原状态,为下一种转换情况做准备
                swap(t[k], t[a * 3 + b]);
            }
        }
    }
    //无法转换到目标状态,返回-1
    return -1;
}

int main()
{
    string c, start;

    for(int i = 0; i < 9; i++)
    {
        cin >> c;
        start += c;           
    }

    cout << bfs(start) << endl;             

    return 0;
}


树的重心

在这里插入图片描述


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

using namespace std;

const int N = 1e5 + 10;
const int M = 2 * N;

int h[N];

int e[M];       //e和ne的空间为2*N
int ne[M];

int idx;
int n;
int ans = N;            //全局的最大最小值
bool st[N];


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




int dfs(int u)
{
    int res = 0;        //表示将该点删掉,每个连通块的最大值
    st[u] = true;           //标记该点已经访问过了
    int sum = 1;            //sum表示当前子树的大小,所以从1开始
    //sum表示将该点删掉后,并且包括该点的,总点数
    
    for (int i = h[u]; i != -1; i = ne[i]) 
    {
        int j = e[i];       
        if (!st[j])         //如果该点没有访问过
        {
            int s = dfs(j);             //s表示当前子树的大小
            res = max(res, s);          //s跟res取max
            sum += s;
        }
    }


    res = max(res, n - sum);
    ans = min(res, ans);
    return sum;
}

int main()
{
    memset(h, -1, sizeof h);
    cin >> n;


    for (int i = 0; i < n - 1; i++)
    {
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);               //无向图
    }

    dfs(2);         //从任意节点出发

    cout << ans << endl;        //ans表示最大的最小值

    return 0;
}

图中点的层次

在这里插入图片描述


#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 100010;

int n, m;
int h[N], e[N], ne[N], idx;          //单向图的e和ne都是N
int d[N];

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

int bfs()
{
    memset(d, -1, sizeof d);        //d表示从1-每个点的最短距离

    queue<int> q;           
    d[1] = 0;
    q.push(1);

    while (q.size())
    {
        int t = q.front();
        q.pop();

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (d[j] == -1)     //表示j这个点没有访问过
            {
                d[j] = d[t] + 1;
                q.push(j);
            }
        }
    }

    return d[n];
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);

    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);          //有向图就设置成单条边
    }

    cout << bfs() << endl;          //直接输出最短路径

    return 0;
}


有向图的拓扑序列

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



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

const int maxn=100010;

int n,m;

int idx,e[maxn],ne[maxn],h[maxn];

int q[maxn];

int d[maxn];

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

bool tuopu()
{
    int hh=0,tt=-1;     //hh表示开头,tt表示结尾

    for(int i=1; i<=n; i++)
        if(!d[i])               //统计入度为0的点
            q[++tt]=i;

    while(tt>=hh)
    {
        int t=q[hh++];
        for(int i=h[t]; i!=-1; i=ne[i])
        {
            int j=e[i];
            d[j]--;
            if(d[j]==0)
            {
                q[++tt]=j;
            }
        }
    }

    if(tt==n-1)     //如果tt==n-1则返回true
        return true;

}



int main()
{
    cin >> n >> m;
    memset(h,-1,sizeof h);
    for(int i=0; i<m; i++)
    {
        int a,b;
        cin >> a >> b;
        add(a,b);
        d[b]++;     //统计每个点的入度个数
    }

    if(!tuopu())
        cout << -1;
    else
    {
        for(int i=0; i<n; i++)
            cout << q[i] << " ";
    }

    return 0;
}

有边数限制的最短路

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

#include<iostream>
#include<cstring>

using namespace std;

const int N = 510, M = 10010;  //注意边数的范围

struct Edge
{
    int a;
    int b;
    int w;
} e[M];
int dist[N];
int back[N];
int n, m, k;

int bellman_ford()
 {
    memset(dist, 0x3f, sizeof dist);        //对dist初始化为0x3f
    dist[1] = 0;
    for (int i = 0; i < k; i++)             //最多经过的边数是k条边,所以就循环k次
    {
        memcpy(back, dist, sizeof dist);        //这里需要备份,因为我们循环其实是k条边,具体原因也不用管,将上一次的dist备份到back
        for (int j = 0; j < m; j++)
        {
            int a = e[j].a, b = e[j].b, w = e[j].w;
            dist[b] = min(dist[b], back[a] + w);        //注意这里是back[a]+w,不是dist[a]+w

        }
    }
    if (dist[n] > 0x3f3f3f3f / 2) return -1  //之所以大于0x3f3f3f3f/2是因为如果倒数第2个点可以更新倒数第1个点,就是表示先到倒2点后从倒2进入倒1,那么倒1的点dist可能表示的比0x3f3f3f3f小一点
    else return dist[n];

}

int main()
{
    scanf("%d%d%d", &n, &m, &k);        //输入n个点,m条边,k个限制
    for (int i = 0; i < m; i++)
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);
        e[i] = {a, b, w};               //搞一个结构
    }
    int res = bellman_ford();           //res表示最终的结果

    if (res == -1)
        puts("impossible");
    else
        cout << res;

    return 0;
}

spfa求最短路

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


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

const int maxn=100010;

int n,m;

int idx,h[maxn],ne[maxn],w[maxn],e[maxn];

int dist[maxn];

bool st[maxn];

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

int spfa()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;

    queue<int> q;
    q.push(1);
    st[1]=true;

    while(q.size())
    {
        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]=true;                        
                    }

                }

        }

    }
    return dist[n];
}

int main()
{
    cin >> n >> m;
    memset(h,-1,sizeof h);

    for(int i=0; i<m; i++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a,b,c);
    }

    int t=spfa();

    if(t==0x3f3f3f3f)
        cout << "impossible" << endl;
    else
        cout << t << endl;

    return 0;
}

spfa判断负环


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

using namespace std;

const int N = 2010, M = 10010;

int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];        //dist[x]表示1~x的最短距离,cnt[x]表示当前最短路的边数
bool st[N];

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

bool spfa()
{
    queue<int> q;               //设置队列q
    //问题求的是去判断能否有负环,并不一定要从1号点开始,所以需要将所有点放入队列q中

    for (int i = 1; i <= n; i ++ )     //先将所有点进行处理一遍
    {
        st[i] = true;
        q.push(i);
    }

    while (q.size())
    {
        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;

                if (cnt[j] >= n) return true;           //cnt[j]>=n,表示从1--j所经过的边数>=n,表示出现了负环,否则表示没有出现负环
                if (!st[j])
                {
                    q.push(j);
                    st[j] = true;
                }
            }
        }
    }

    return false;
}

int main()
{
    scanf("%d%d", &n, &m);             //n个点,m条边

    memset(h, -1, sizeof h);            //h 初始化为-1

    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c);
    }

    if (spfa()) puts("Yes");
    else puts("No");

    return 0;
}



Dijkstra求最短路 I


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

const int maxn=511;

int dist[maxn];

int g[maxn][maxn];

int n,m;

bool st[maxn];

int dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;

    for(int i=1; i<=n-1; i++)
    {
        int t=-1;
        for(int j=1; j<=n; j++)
            if(!st[j] && (t==-1 || dist[t]>dist[j]))
                t=j;

        st[t]=true;
        for(int j=1; j<=n; j++)
            if(dist[j]>dist[t]+g[t][j])
                dist[j]=dist[t]+g[t][j];

    }

    if(dist[n]==0x3f3f3f3f)
        return -1;

    return dist[n];

}

int main()
{
    cin >> n >> m;
    memset(g,0x3f,sizeof g);
    for(int i=0; i<m; i++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        g[a][b]=min(g[a][b],c);
    }

    cout << dijkstra() << endl;

    return 0;
}

Dijkstra求最短路 2


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

const int maxn=150010;

typedef pair<int,int> PII;

int n,m;

int idx,h[maxn],ne[maxn],e[maxn],w[maxn];

int dist[maxn];

bool st[maxn];

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

int dijkstra()
{
    memset(dist,0x3f,sizeof dist);
    dist[1]=0;
    priority_queue<PII ,vector<PII>,greater<PII>> heap;             //优先队列

    heap.push({0,1});          //到1这个点,距离为0

    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();

        int ver=t.second;      //ver表示中间的点

        if(st[ver])
            continue;

        st[ver]=true;

        for(int i=h[ver]; i!=-1; i=ne[i])
        {
            int j=e[i];

            if(dist[j]>dist[ver]+w[i])
            {
                dist[j]=dist[ver]+w[i];
                heap.push({dist[j],j});
            }
        }

    }

    if(dist[n]==0x3f3f3f3f)
        return -1;

    return dist[n];

}

int main()
{
    cin >> n >> m;
    memset(h,-1,sizeof h);

    for(int i=0; i<m; i++)
    {
        int a,b,c;
        cin >> a >> b >> c;         //输入a,b,c
        add(a,b,c);
    }

    cout << dijkstra();     //直接输出

    return 0;

}

Floyd求最短路

在这里插入图片描述


#include<iostream>
using namespace std;

const int maxn=211;
const int eps=1e9;

int n,m,Q;

int d[maxn][maxn];

void floyd()
{
    for(int k=1; k<=n; k++)
        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 >> Q;

    for(int i=1; i<=n; i++)
        for(int j=1; j<=n; j++)
            if(i==j)
                d[i][j]=0;
            else
                d[i][j]=eps;

    for(int i=0; i<m; i++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        d[a][b]=min(d[a][b],c);
    }

    floyd();

    while(Q--)
    {
        int a,b;
        cin >> a >> b;
        int t=d[a][b];
        if(t>eps/2)
            cout << "impossible" << endl;
        else
            cout << t << endl;

    }
    return 0;
}

Prim算法求最小生成树

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

选中第1个点

在这里插入图片描述

用该点去更新其他点

在这里插入图片描述

选择距离1号点距离最近的点,并且用2号点去更新其他点的距离

在这里插入图片描述

找到这里面距离最近的点,也就是3号点

在这里插入图片描述

最后一步将4选中

在这里插入图片描述

最后的生成树

在这里插入图片描述



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

using namespace std;

const int N = 510, INF = 0x3f3f3f3f;

int n, m;
int g[N][N];
int dist[N];
bool st[N];


int prim()
{
    memset(dist, 0x3f, sizeof dist);            //将所有距离初始化为正无穷

    int res = 0;            //最小生成树的边权之和
    for (int i = 0; i < n; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!st[j] && (t == -1 || dist[t] > dist[j]))       //找到集合外距离最近的点
                t = j;

        if (i && dist[t] == INF) return INF;        //如果不是第1个点,并且如果dist[n]为正无穷,表示没有找到合适的点,则直接返回正无穷

        if (i) res += dist[t];      //只要不是第1条边,则将dist[t
        
        st[t] = true;

        for (int j = 1; j <= n; j ++ ) 
            dist[j] = min(dist[j], g[t][j]);        //用t更新其他点到集合的距离
    }

    return res;
}


int main()
{
    scanf("%d%d", &n, &m);      //n个点,m条边

    memset(g, 0x3f, sizeof g);

    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    int t = prim();     //t表示最小生成树的边权

    if (t == INF) puts("impossible");
    else printf("%d\n", t);

    return 0;
}


Kruskal算法求最小生成树


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

using namespace std;

const int N = 100010, M = 200010, INF = 0x3f3f3f3f;

int n, m;
int p[N];

struct Edge
{
    int a, b, w;

    bool operator< (const Edge &W)const
    {
        return w < W.w;                            //按照边权的升序排列
    }
}edges[M];              //注意边权个数的上届

int find(int x)     //寻找根节点
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

int kruskal()
{
    sort(edges, edges + m);         //对边权排序

    for (int i = 1; i <= n; i ++ ) p[i] = i;    // 初始化并查集

    int res = 0, cnt = 0;           //cnt表示边数,res表示边权
    for (int i = 0; i < m; i ++ )       //枚举每条边
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;

        a = find(a), b = find(b);
        if (a != b)
        {
            p[a] = b;
            res += w;
            cnt ++ ;
        }
    }

    if (cnt < n - 1) return INF;   //n个点至少需要n-1条边,如果计算的生成树中的边数小于n-1则直接返回INF,表示没有最小生成树
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);          //n个点,m条边

    for (int i = 0; i < m; i ++ )               //结构来存放边的信息
    {
        int a, b, w;
        scanf("%d%d%d", &a, &b, &w);            //输入a,b点和边权
        edges[i] = {a, b, w};
    }

    int t = kruskal();      //调用kruskal()

    if (t == INF) puts("impossible");
    else printf("%d\n", t);

    return 0;
}



染色法判定二分图

在这里插入图片描述


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

using namespace std;

const int N = 100010, M = 200010;

int n, m;
int h[N], e[M], ne[M], idx;
int color[N];

void add(int a, int b)          //邻接表存储
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

bool dfs(int u, int c)
{
    color[u] = c;           //标记u的颜色为c

    for (int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!color[j])              //如果当前这个点没有染过颜色
        {
            if (!dfs(j, 3 - c)) return false;           //则需要将j对应的点染成另一种颜色
        }
        else if (color[j] == c) return false;           //如果一条边上的两种颜色相同,也是返回false
    }

    return true;
}

int main()
{
    scanf("%d%d", &n, &m);      //输入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;           //初始化flag为true,表示没有矛盾发生
    
    for (int i = 1; i <= n; i ++ )          //遍历n个点
        if (!color[i])      //如果i没有染色
        {
            if (!dfs(i, 1))             //用dfs将i所在的连通块进行全部染色
            {
                flag = false;       //如果出现矛盾,则让flag为false
                break;
            }
        }

    if (flag) puts("Yes");      //flag为true,直接输出yes;否则输出no
    else puts("No");

    return 0;
}


二分图的最大匹配


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

using namespace std;

const int N = 510, M = 100010;

int n1, n2, m;
int h[N], e[M], ne[M], idx;
int match[N];           //右边对应的点
bool st[N];

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

bool find(int x)        
{
    for (int i = h[x]; i != -1; i = ne[i])
    {
        int j = e[i];
        if (!st[j])             //这个妹子之前没有考虑过
        {
            st[j] = true;
            if (match[j] == 0 || find(match[j]))            //如果这个妹子没有匹配任何男生,或者可以为追求该妹子的男的找到下家
            {
                match[j] = x;          //则该妹子可以匹配x
                return true;
            }
        }
    }

    return false;
}

int main()
{
    scanf("%d%d%d", &n1, &n2, &m);          //n1,n2点,m条边

    memset(h, -1, sizeof h);        //h表示-1

    while (m -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b);
    }

    int res = 0;            //匹配的数量
    
    for (int i = 1; i <= n1; i ++ )         //枚举左边的点
    {
        memset(st, false, sizeof st);       //将所有妹子清空
        if (find(i)) res ++ ;
    }

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

    return 0;
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值