搜索 ( 八数码问题详解:BFS,A*,IDA* )——Eight ( POJ 1077 )

参考文章:
1. DBFS,A*,IDA*,转自链接 (侵删)
2. 一份非常详细的实验报告:http://blog.csdn.net/ray58750034/article/details/599897#comments

  • 题目链接:
    http://poj.org/problem?id=2449

  • 分析:
    又称为九宫格问题,给出一个状态的九宫格,判断是否有解,如果有则输出解。

  • 八数码问题有无解的结论:

    1. 一个状态表示成一维的形式,求出除0之外所有数字的逆序数之和(每个数字前面比它大的数字的个数的和,称为这个状态的逆序)。

    2. 若两个状态的逆序数奇偶性相同,则可相互到达,否则不可相互到达。由于原始状态的逆序为0(偶数),则逆序为偶数的状态有解。

      证明:当左右移动空格时,逆序数不变。当上下移动空格时,相当于将一个数字向前(或向后)移动两格,跳过 的这两个数字要么都比它大(小),逆序可能±2;要么一个较大一个较小,逆序不变。所以可得结论:只要是相互可达的两个状态,它们的逆序奇偶性相同。详细的证明请参考:
      http://blog.csdn.net/tiaotiaoyly/article/details/2008233

  • 康拓展开(将每一种排列都康拓展开,得到与其唯一对应的一个数,是对于全排列的一种很好的哈希函数):

    公式: X=an×(n1)!+an1×(n2)!+...+ai×(i1)!+...+a2×1!+a1×0! 其中,ai表示的是第i-1个数(从0开始数),在这个排列中的大小位置(从0开始计算)。

    代码如下:

int fac[] = { 1,1,2,6,24,120,720,5040,40320 };//打表表示前几个数的阶乘。
int Cantor( int s[])//参数为排列。
{
    int sum = 0;
    for(int i = 0;i<9;i++)
    {
        int cnt = 0;
        for(int j=i+1;j<9;j++)
        {
            if( s[i] > s[j] ) cnt++;
        }
        sum+=(cnt*fac[9-i-1]);
    }
    return sum+1;
}
  • 通过康拓逆展开生成与其对应的全排列:

    如果已知一个排列的康拓展开数,那么可以使用辗转相除的方法得到这个排列中的每一个 ai
    这里写图片描述

    如果排列中的元素为A,B,C,D,那么a4 = 3表示剩余元素为4的时候它排在第三位(均从第0位开始计数),即D;a3 = 1表示剩余元素为3的时候它排再第一位,即B;a2 = 0表示剩余元素为2的时候它排在第0位,即A,最后一个a1就是C了。

  • BFS + 康拓展开 解决八数码问题:

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

// 正向广度搜索

using namespace std;

const int maxn = 1000000;
int fac[] = { 1,1,2,6,24,120,720,5040,40320 };
bool flag [maxn];
int dir[4][2]={{-1,0},{1,0},{0,-1},{0,1}};
string cmd = "udlr";
int aim=46234;

int Cantor( int s[])
{
    int sum = 0;
    for(int i = 0;i<9;i++)
    {
        int cnt = 0;
        for(int j=i+1;j<9;j++)
        {
            if( s[i] > s[j] ) cnt++;
        }
        sum+=(cnt*fac[9-i-1]);
    }
    return sum+1;
}

struct node
{
    int s[9];
    int loc;
    int status;
    string path;
};

node origin;
string path;


int BFS()
{
    queue <node> q;
    while ( !q.empty() )
        q.pop();

    node cur,temp;

    q.push(origin);

    int x,y;
    while( !q.empty() )
    {
        cur = q.front();
        q.pop();

        if( cur.status == aim )
        {
            path = cur.path;
            return 1;
        }
        x = cur.loc/3;
        y = cur.loc%3;
        for(int i=0;i<4;i++)
        {
            int tx = x + dir[i][0];
            int ty = y + dir[i][1];

            if( tx<0 || ty <0 || tx>2 || ty>2 ) continue;

            temp = cur;
            temp.loc = tx*3 + ty;

            temp.s[cur.loc] = temp.s[temp.loc];
            temp.s[temp.loc] = 0;
            temp.status = Cantor(temp.s);

            if( !flag[temp.status] )
            {
                flag[temp.status] = 1;
                temp.path = temp.path + cmd[i];
                if( temp.status == aim )
                {
                    path = temp.path;
                    return 1;
                }
                q.push(temp);
            }
        }
    }
    return 0;
}



int main()
{
    char ch;
    while(cin>>ch)
    {
        if(ch == 'x')
        {
            origin.s[0] = 0;
            origin.loc = 0;
        }
        else origin.s[0] = ch - '0';
        for(int i=1;i<9;i++)
        {
            cin >> ch;
            if( ch == 'x' )
            {
            origin.s[i] = 0;
            origin.loc = i;
            }
            else origin.s[i] = ch - '0';
        }
        origin.status = Cantor( origin.s );
        memset( flag , 0 ,sizeof(flag) );

        if(BFS())
            cout<<path<<endl;
        else
             printf("unsolvable\n");
    }
    return 0;
}
  • 双向广搜 :

    由于目标态和初始态都已知,可以采用从两个状态同时开始搜,当搜到同一个节点时,搜索结束,将两边的步数加起来输出。这道题里在每个节点,用一个值标记,此节点是由哪个状态访问的,故只需用一个队列交替扩展。双向广搜会少扩展许多节点,时间效率得到大幅提升。

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <queue>
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
    int sta,pos,step;
    int visit;
}st[MAXN],source,target;
int temp[10],tar[10],sou[10];
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int num;
int convert(int a[],state &b)
{
    b.sta=0;
    for(int i = 0; i < 9; i ++) 
    {   
        if(a[i]!=0)
            b.sta |=((a[i]-1)<<(24-i*3));
        else
        {
            b.pos = i;
            b.sta |=(a[i]<<(24-i*3));
        }
    }
    return 1;
}
state exchange(state a,int pos)
{
    int temp = 7<<((9-pos)*3);
    state s;
    s.sta = a.sta;
    temp = temp & a.sta;
    temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
    s.sta |= temp;
    s.sta &= ~(7<<((9-pos)*3));
    s.pos = pos-1;
    return s;
}
int search(state a)
{
    int index = a.sta%MAXN;
    bool flag = true;
    while(flag)
    {
        if(!st[index].sta)
        {
            st[index].sta = a.sta;
            st[index].pos = a.pos;
            flag = false;
        }
        else if(!(st[index].sta^a.sta)&&!(st[index].pos^a.pos))
            flag = false;
        else
            index = (index+1)%MAXN;
    }
    return index;
}
int main()
{
    freopen("in.txt","r",stdin);
    clock_t start,end;
    for(int j=0;j<8;j++)temp[j] = j+1;
    temp[8]=0;
    convert(temp,target);
    while(1)
    {
        int i = 0;
        memset(st,0,sizeof(st));
        char ch;
        while((ch=getchar())!='\n')
        {
            if(ch<='9'&&ch>='0')
                sou[i++] = ch - '0';
            else if(ch=='x')
                sou[i++] =0;
        }
        convert(sou,source);
        start = clock();
        i = search(source);
        queue<int>q;
        q.push(i);
        i = search(target);
        st[i].visit = 1;
        st[i].step = 1;
        q.push(i);
        if(!(source.sta^target.sta)&&!(source.pos^target.pos))
        {
            printf("0\n");
            while(!q.empty())q.pop();
                continue;
        }
        int index;
        int count = 0;
        bool isSolve = false;
        while(!q.empty()&&!isSolve)
        {
            count ++;
            index = q.front();
            for(int j = 0; j < 4; j ++)
            {
                if(d[st[index].pos][j])
                {
                    int flag = search(exchange(st[index],d[st[index].pos][j]));
                    if(!st[flag].step)
                    {
                        st[flag].step = st[index].step + 1; 
                        st[flag].visit = st[index].visit;
                        q.push(flag);
                    }   
                    else
                    {
                        if(st[flag].visit^st[index].visit)
                        {
                            isSolve = true;
                            printf("%d\n",st[index].step+st[flag].step);
                        }
                    }
                }
            }
            q.pop();
        }
        while(!q.empty())q.pop();
        end = clock();
        printf("Time:%dms\nstate number:%d\n",end-start,count);
    }
    system("pause");
        return 0;
}
  • A*搜索:

    有两种可行的启发函数: 不在位数(difference)与曼哈顿距离(manhattan)

    在每一个节点中,加一个int型变量存储此节点的估价。此时用优先队列存储节点。但是若启发函数效率不高,减少的扩展节点的时间可能还不足以抵过小顶堆调整的时间,结果就是时间效率可能比普通的bfs还差。

    基于不在位函数的A*搜索:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <queue>
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
    int sta,pos,step;
    int f;
}st[MAXN],source,target;
int temp[10],tar[10],sou[10];
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int num;
int h(state a)
{
    int temp = target.sta;
    int cnt=0;
    for(int i = 0;i < 9; i ++)
    {
        if(a.pos==target.pos)
        {
            if(!(((temp>>(3*i))&7)^((a.sta>>(3*i))&7)))
                cnt++;
        }
        else
        {
            if((!(((temp>>(3*i))&7)^((a.sta>>(3*i))&7)))&&((a.sta>>(3*i))&7))
                cnt++;
        }
    }
    return 9-cnt;
}
struct cmp
{
    bool operator () (int u, int v)
    {
        return st[u].f > st[v].f;       
    }
};
int convert(int a[],state &b)
{
    b.sta=0;
    for(int i = 0; i < 9; i ++) 
    {   
        if(a[i]!=0)
            b.sta |=((a[i]-1)<<(24-i*3));
        else
        {
            b.pos = i;
            b.sta |=(a[i]<<(24-i*3));
        }
    }
    return 1;
}
state exchange(state a,int pos)
{
    int temp = 7<<((9-pos)*3);
    state s;
    s.sta = a.sta;
    temp = temp & a.sta;
    temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
    s.sta |= temp;
    s.sta &= ~(7<<((9-pos)*3));
    s.pos = pos-1;
    return s;
}
int search(state a)
{
    int index = a.sta%MAXN;
    bool flag = true;
    while(flag)
    {
        if(!st[index].sta)
        {
            st[index].sta = a.sta;
            st[index].pos = a.pos;
            flag = false;
        }
        else if(!(st[index].sta^a.sta)&&!(st[index].pos^a.pos))
            flag = false;
        else
            index = (index+1)%MAXN;
    }
    return index;
}
int main()
{
    freopen("in.txt","r",stdin);
    clock_t start,end;
    for(int j=0;j<8;j++)temp[j] = j+1;
    temp[8]=0;
    convert(temp,target);
    while(1)
    {
        int i = 0;
        memset(st,0,sizeof(st));
        char ch;
        while((ch=getchar())!='\n')
        {
            if(ch<='9'&&ch>='0')
                sou[i++] = ch - '0';
            else if(ch=='x')
                sou[i++] =0;
        }
        convert(sou,source);
        start = clock();
        i = search(source);
        st[i].f = h(st[i]);
        priority_queue<int,vector<int>,cmp>q;
        q.push(i);
        int index;
        int count = 0;
        while(!q.empty())
        {               
            count++;
            index = q.top();
            q.pop();                //!!!!
            if(!(st[index].sta^target.sta)&&st[index].pos == target.pos)
            {
                printf("%d\n",st[index].step);
                break;
            }
            for(int j = 0; j < 4; j ++)
            {
                if(d[st[index].pos][j])
                {
                    int flag = search(exchange(st[index],d[st[index].pos][j]));
                    if(!st[flag].step||st[flag].step > st[index].step + 1)
                    {
                        st[flag].step = st[index].step + 1; 
                        st[flag].f = st[flag].step + h(st[flag]);
                        q.push(flag);
                    }   
                }
            }
        }
        while(!q.empty())q.pop();
        end = clock();
        printf("Time:%dms\nstate number:%d\n",end-start,count);
    }
    system("pause");
        return 0;
}

基于 manhattan距离函数的A*搜索:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <queue>
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
    int sta,pos,step;
    int f;
}st[MAXN],source,target;
int temp[10],tar[10],sou[10];
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int num;
int manhattan[10][10] = //第i个数及其所处不同位置的Manhattan路径长度
{
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1},
{-1, 0, 1, 2, 1, 2, 3, 2, 3, 4},
{-1, 1, 0, 1, 2, 1, 2, 3, 2, 3},
{-1, 2, 1, 0, 3, 2, 1, 4, 3, 2},
{-1, 1, 2, 3, 0, 1, 2, 1, 2, 3},
{-1, 2, 1, 2, 1, 0, 1, 2, 1, 2},
{-1, 3, 2, 1, 2, 1, 0, 3, 2, 1},
{-1, 2, 3, 4, 1, 2, 3, 0, 1, 2},
{-1, 3, 2, 3, 2, 1, 2, 1, 0, 1},
{-1, 4, 3, 2, 3, 2, 1, 2, 1, 0}

};
int h(state a)
{
    int cnt=0;
    for(int i = 0;i < 9; i ++)
    {
        if(a.pos != i)
            cnt += manhattan[((a.sta>>(3*(8-i)))&7)+1][i+1];
    }
    return cnt;
}
class cmp
{
      public:
    bool operator () (int u, int v)
    {
        return st[u].f > st[v].f;       
    }
};
int convert(int a[],state &b)
{
    b.sta=0;
    for(int i = 0; i < 9; i ++) 
    {   
        if(a[i]!=0)
            b.sta |=((a[i]-1)<<(24-i*3));
        else
        {
            b.pos = i;
            b.sta |=(a[i]<<(24-i*3));
        }
    }
    return 1;
}
state exchange(state a,int pos)
{
    int temp = 7<<((9-pos)*3);
    state s;
    s.sta = a.sta;
    temp = temp & a.sta;
    temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
    s.sta |= temp;
    s.sta &= ~(7<<((9-pos)*3));
    s.pos = pos-1;
    return s;
}
int search(state a)
{
    int index = a.sta%MAXN;
    bool flag = true;
    while(flag)
    {
        if(!st[index].sta)
        {
            st[index].sta = a.sta;
            st[index].pos = a.pos;
            flag = false;
        }
        else if(!(st[index].sta^a.sta)&&!(st[index].pos^a.pos))
            flag = false;
        else
            index = (index+1)%MAXN;
    }
    return index;
}
int main()
{
    freopen("in.txt","r",stdin);
    clock_t start,end;
    for(int j=0;j<8;j++)temp[j] = j+1;
    temp[8]=0;
    convert(temp,target);
    while(1)
    {
        int i = 0;
        memset(st,0,sizeof(st));
        char ch;
        while((ch=getchar())!='\n')
        {
            if(ch<='9'&&ch>='0')
                sou[i++] = ch - '0';
            else if(ch=='x')
                sou[i++] =0;
        }
        convert(sou,source);
        start = clock();
        i = search(source);
        st[i].f = h(st[i]);
        priority_queue<int,vector<int>,cmp>q;
        q.push(i);
        int index;
        int count = 0;
        while(!q.empty())
        {               
            count++;
            index = q.top();
            q.pop();                //!!!!
            if(!(st[index].sta^target.sta)&&st[index].pos == target.pos)
            {
                printf("%d\n",st[index].step);
                break;
            }
            for(int j = 0; j < 4; j ++)
            {
                if(d[st[index].pos][j])
                {
                    int flag = search(exchange(st[index],d[st[index].pos][j]));
                    if(!st[flag].step||st[flag].step > st[index].step + 1)
                    {
                        st[flag].step = st[index].step + 1; 
                        st[flag].f = st[flag].step + h(st[flag]);
                        q.push(flag);
                    }   
                }
            }
        }
        while(!q.empty())q.pop();
        end = clock();
        printf("Time:%dms\nstate number:%d\n",end-start,count);
    }
    system("pause");
        return 0;
}
  • IDA*搜索:

    由于普通的深搜在此问题上,要么搜索到错误的结果,要么需要搜索所有的状态,才能确定是否是最优,故在这里使用IDA*。IDA*是一种迭代加深的深度搜索,若在此深度下没有搜到目标点,则将深度加一重新搜索。无须状态判重,无需估价排序,用不到哈希表,堆上也不必应用,空间需求变的超级少,实现也最简单。在深搜过程中,根据启发函数做剪枝,可以使效率达到很高。另外在求路径的时候,IDA*也是最方便的。

    基于不在为函数的IDA*搜索:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <queue>
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
    int sta,pos;
}source,target;
int temp[10],tar[10],sou[10];
int pathLimit;
int cnt;
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int h(state a)
{
    int temp = target.sta;
    int cnt=0;
    for(int i = 0;i < 9; i ++)
    {
        if(a.pos==target.pos)
        {
            if(!(((temp>>(3*i))&7)^((a.sta>>(3*i))&7)))
                cnt++;
        }
        else
        {
            if(!(((temp>>(3*i))&7)^((a.sta>>(3*i))&7))&&((a.sta>>(3*i))&7))
                cnt++;
        }
    }
    return 9-cnt;
}
int convert(int a[],state &b)
{
    b.sta=0;
    for(int i = 0; i < 9; i ++) 
    {   
        if(a[i]!=0)
            b.sta |=((a[i]-1)<<(24-i*3));
        else
        {
            b.pos = i;
            b.sta |=(a[i]<<(24-i*3));
        }
    }
    return 1;
}
state exchange(state a,int pos)
{
    int temp = 7<<((9-pos)*3);
    state s;
    s.sta = a.sta;
    temp = temp & a.sta;
    temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
    s.sta |= temp;
    s.sta &= ~(7<<((9-pos)*3));
    s.pos = pos-1;
    return s;
}
bool IDAStar(state &a,int depth,int diff,int prepos)
{
    cnt++;
    if(!(a.sta^target.sta)&&a.pos == target.pos)
    {
        printf("%d\n",depth);
        return true;
    }
    if(depth >= pathLimit) return false;
    if( depth + diff > pathLimit ) return false;  
    for(int j = 0; j < 4; j ++)
    {
        if(d[a.pos][j] == prepos+1) continue;
        if(d[a.pos][j])
        {
            state next = exchange(a,d[a.pos][j]);
            if(IDAStar(next,depth+1, h(next),a.pos))
                return true;
        }
    }
    return false;
}
int main()
{
    freopen("in.txt","r",stdin);
    clock_t start,end;
    int diff = 0;
    for(int j=0;j<8;j++)temp[j] = j+1;
    temp[8]=0;
    convert(temp,target);
    while(1)
    {
        int i = 0;
        char ch;
        while((ch=getchar())!='\n')
        {
            if(ch<='9'&&ch>='0')
                sou[i++] = ch - '0';
            else if(ch=='x')
                sou[i++] =0;
        }
        start = clock();
        cnt = 0;
        convert(sou,source);
        pathLimit = h(source);
        diff = pathLimit;
        while(!IDAStar(source,0,diff,-1))pathLimit++; 
        end = clock();
        printf("Time:%dms\nstate number:%d\n",end-start,cnt);
    }
    system("pause");
        return 0;
}

基于manhattan距离函数的搜索:

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <queue>
#define FUCK puts("fuck!")
#define STOP system("pause")
#define MAXN 388211
using namespace std;
struct state{
    int sta,pos;
}source,target;
int temp[10],tar[10],sou[10];
int pathLimit;
int d[9][4]={{0,4,0,2},{0,5,1,3},{0,6,2,0},{1,7,0,5},{2,8,4,6},{3,9,5,0},{4,0,0,8},{5,0,7,9},{6,0,8,0}};
int manhattan[10][10] = //第i个数及其所处不同位置的Manhattan路径长度
{
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1},
{-1, 0, 1, 2, 1, 2, 3, 2, 3, 4},
{-1, 1, 0, 1, 2, 1, 2, 3, 2, 3},
{-1, 2, 1, 0, 3, 2, 1, 4, 3, 2},
{-1, 1, 2, 3, 0, 1, 2, 1, 2, 3},
{-1, 2, 1, 2, 1, 0, 1, 2, 1, 2},
{-1, 3, 2, 1, 2, 1, 0, 3, 2, 1},
{-1, 2, 3, 4, 1, 2, 3, 0, 1, 2},
{-1, 3, 2, 3, 2, 1, 2, 1, 0, 1},
{-1, 4, 3, 2, 3, 2, 1, 2, 1, 0}

};
int h(state a)
{
    int cnt=0;
    for(int i = 0;i < 9; i ++)
    {
        if(a.pos != i)
            cnt += manhattan[((a.sta>>(3*(8-i)))&7)+1][i+1];
    }
    return cnt;
}
int convert(int a[],state &b)
{
    b.sta=0;
    for(int i = 0; i < 9; i ++) 
    {   
        if(a[i]!=0)
            b.sta |=((a[i]-1)<<(24-i*3));
        else
        {
            b.pos = i;
            b.sta |=(a[i]<<(24-i*3));
        }
    }
    return 1;
}
state exchange(state a,int pos)
{
    int temp = 7<<((9-pos)*3);
    state s;
    s.sta = a.sta;
    temp = temp & a.sta;
    temp = ((temp>>((9-pos)*3))<<((9-a.pos-1)*3));
    s.sta |= temp;
    s.sta &= ~(7<<((9-pos)*3));
    s.pos = pos-1;
    return s;
}
bool IDAStar(state &a,int depth,int diff,int prepos)
{
    if(!(a.sta^target.sta)&&a.pos == target.pos)
    {
        printf("%d\n",depth);
        return true;
    }
    if(depth > pathLimit) return false;
    if( depth + diff > pathLimit ) return false;  
    for(int j = 0; j < 4; j ++)
    {
        if(d[a.pos][j] == prepos+1) continue;
        if(d[a.pos][j])
        {
            state next = exchange(a,d[a.pos][j]);
            if(IDAStar(next,depth+1, h(next),a.pos))
                return true;
        }
    }
    return false;
}
int main()
{
    freopen("in.txt","r",stdin);
    clock_t start,end;
    int diff = 0;
    for(int j=0;j<8;j++)temp[j] = j+1;
    temp[8]=0;
    convert(temp,target);
    while(1)
    {
        int i = 0;
        char ch;
        while((ch=getchar())!='\n')
        {
            if(ch<='9'&&ch>='0')
                sou[i++] = ch - '0';
            else if(ch=='x')
                sou[i++] =0;
        }
        start = clock();
        convert(sou,source);
        pathLimit = h(source);
        diff = pathLimit;
        while(!IDAStar(source,0,diff,-1))pathLimit++; 
        end = clock();
        printf("Time:%dms\ndepthlimit:%d\n",end-start,pathLimit);
    }
    system("pause");
        return 0;
}
  • 总结:
    从效率上来看 IDA* > A* > DBFS > BFS+Canton >BFS ,其中,manhattan估价函数优余difference估价函数。
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值