【codeup】1959: 全排列 及全排列算法详解

题目描述

给定一个由不同的小写字母组成的字符串,输出这个字符串的所有全排列。
我们假设对于小写字母有'a' < 'b' < ... < 'y' < 'z',而且给定的字符串中的字母已经按照从小到大的顺序排列。

输入

输入只有一行,是一个由不同的小写字母组成的字符串,已知字符串的长度在1到6之间。

输出

输出这个字符串的所有排列方式,每行一个排列。要求字母序比较小的排列在前面。字母序如下定义:
已知S = s1s2...sk , T = t1t2...tk,则S < T 等价于,存在p (1 <= p <= k),使得
s1 = t1, s2 = t2, ..., sp - 1 = tp - 1, sp < tp成立。

注意每组样例输出结束后接一个空行。

样例输入

xyz

样例输出

xyz
xzy
yxz
yzx
zxy
zyx

提示

 

用STL中的next_permutation会非常简洁。

 

 

思路:

由于题目提示使用next_permutation会简洁,所以这里我们使用此方法。

 1 #include<iostream>
 2 #include<stdio.h>
 3 #include<queue>
 4 #include<string>
 5 #include<string.h>
 6 #include<algorithm>
 7 using namespace std;
 8 
 9 char a[10];
10 
11 int main()
12 {
13     int n;
14     while(scanf("%s",a)!=EOF)
15     {
16         n=strlen(a);
17         do
18         {
19             printf("%s\n",a);
20         }while(next_permutation(a,a+n));
21         puts("");
22     }
23     return 0;
24 }

C++/STL中定义的next_permutation和prev_permutation函数是非常灵活且高效的一种方法,它被广泛的应用于为指定序列生成不同的排列。

next_permutation函数将按字母表顺序生成给定序列的下一个较大的排列,直到整个序列为降序为止。

prev_permutation函数与之相反,是生成给定序列的上一个较小的排列。

所谓“下一个”和“上一个”,举一个简单的例子:

  对序列 {a, b, c},每一个元素都比后面的小,按照字典序列,固定a之后,a比bc都小,c比b大,它的下一个序列即为{a, c, b},而{a, c, b}的上一个序列即为{a, b, c},同理可以推出所有的六个序列为:{a, b, c}、{a, c, b}、{b, a, c}、{b, c, a}、{c, a, b}、{c, b, a},其中{a, b, c}没有上一个元素,{c, b, a}没有下一个元素。

二者原理相同,仅遍例顺序相反,这里仅以next_permutation为例介绍算法。

(1) int 类型的next_permutation

复制代码
int main()
{
    int a[3];
    a[0]=1;a[1]=2;a[2]=3;
    do
    {
        cout<<a[0]<<" "<<a[1]<<" "<<a[2]<<endl;
    }while (next_permutation(a,a+3)); //参数3指的是要进行排列的长度

//如果存在a之后的排列,就返回true。如果a是最后一个排列没有后继,返回false,每执行一次,a就变成它的后继
}
复制代码

输出:
 1 2 3
 1 3 2
 2 1 3
 2 3 1
 3 1 2
 3 2 1

如果改成

 1 while(next_permutation(a,a+2)); 

则输出:
 1 2 3
 2 1 3
只对前两个元素进行字典排序

显然,如果改成

 1 while(next_permutation(a,a+1));  

则只输出:1 2 3
 
若排列本来就是最大的了没有后继,则next_permutation执行后,会对排列进行字典升序排序,相当于循环
 

1 int list[3]={3,2,1};
2 next_permutation(list,list+3);
3 cout<<list[0]<<" "<<list[1]<<" "<<list[2]<<endl;

输出: 1 2 3
 
(2) char 类型的next_permutation
 

复制代码
int main()
{
    char ch[205];
    cin >> ch;
 
    sort(ch, ch + strlen(ch) );
    //该语句对输入的数组进行字典升序排序。如输入9874563102
    cout<<ch;//将输出0123456789,这样就能输出全排列了
 
    char *first = ch;
    char *last = ch + strlen(ch);
 
    do {
        cout<< ch << endl;
    }while(next_permutation(first, last));
     return 0;
}
 
//这样就不必事先知道ch的大小了,是把整个ch字符串全都进行排序
//若采用 while(next_permutation(ch,ch+5)); 如果只输入1562,就会产生错误,因为ch中第五个元素指向未知
//若要整个字符串进行排序,参数5指的是数组的长度,不含结束符
复制代码

(3) string 类型的next_permutation
 

复制代码
int main()
{
    string line;
    while(cin>>line&&line!="#")
    {
        if(next_permutation(line.begin(),line.end())) //从当前输入位置开始
        cout<<line<<endl;
        else cout<<"Nosuccesor\n";
    }
}
复制代码
复制代码
int main()
{
    string line;
    while(cin>>line&&line!="#")
    {
        sort(line.begin(),line.end());//全排列
        cout<<line<<endl;
        while(next_permutation(line.begin(),line.end()))
        cout<<line<<endl;
    }
}
复制代码

 

 next_permutation 自定义比较函数
 

复制代码
#include<iostream> //poj 1256 Anagram
#include<cstring>
#include<algorithm>
using namespace std;
int cmp(char a,char b) //'A'<'a'<'B'<'b'<...<'Z'<'z'.
{
    if(tolower(a)!=tolower(b))
        return tolower(a)<tolower(b);
    else
        return a<b;
}
int main()
{
    char ch[20];
    int n;
    cin>>n;
    while(n--)
    {
        scanf("%s",ch);
        sort(ch,ch+strlen(ch),cmp);
        do
        {
            printf("%s\n",ch);
        }while(next_permutation(ch,ch+strlen(ch),cmp));
    }
    return 0;
}
复制代码

 

用next_permutation和prev_permutation求排列组合很方便,但是要记得包含头文件#include <algorithm>。

虽然最后一个排列没有下一个排列,用next_permutation会返回false,但是使用了这个方法后,序列会变成字典序列的第一个,如cba变成abc。prev_permutation同理。

 

 

 

 

 

全排列生成算法

对于给定的集合A{a1,a2,...,an},其中的n个元素互不相同,如何输出这n个元素的所有排列(全排列)。

递归算法

这里以A{a,b,c}为例,来说明全排列的生成方法,对于这个集合,其包含3个元素,所有的排列情况有3!=6种,对于每一种排列,其第一个元素有3种选择a,b,c,对于第一个元素为a的排列,其第二个元素有2种选择b,c;第一个元素为b的排列,第二个元素也有2种选择a,c,……,依次类推,我们可以将集合的全排列与一棵多叉树对应。如下图所示

在此树中,每一个从树根到叶子节点的路径,就对应了集合A的一个排列。通过递归算法,可以避免多叉树的构建过程,直接生成集合A的全排列,代码如下。

 

 1 template <typename T>
 2 inline void swap(T* array, unsigned int i, unsigned int j)
 3 {
 4     T t = array[i];
 5     array[i] = array[j];
 6     array[j] = t;
 7 }
 8 
 9 /*
10  * 递归输出序列的全排列
11  */
12 void FullArray(char* array, size_t array_size, unsigned int index)
13 {
14     if(index >= array_size)
15     {
16         for(unsigned int i = 0; i < array_size; ++i)
17         {
18             cout << array[i] << ' ';
19         }
20 
21         cout << '\n';
22 
23         return;
24     }
25 
26     for(unsigned int i = index; i < array_size; ++i)
27     {
28         swap(array, i, index);
29 
30         FullArray1(array, array_size, index + 1);
31 
32         swap(array, i, index);
33     }
34 }

 

 
该算法使用原始的集合数组array作为参数代码的28~32行,将i位置的元素,与index位置的元素交换的目的是使得array[index + 1]到array[n]的所有元素,对应当前节点的后继结点,递归调用全排列生成函数。调用结束之后还需要回溯将交换位置的元素还原,以供其他下降路径使用。

字典序

全排列生成算法的一个重要思路,就是将集合A中的元素的排列,与某种顺序建立一一映射的关系,按照这种顺序,将集合的所有排列全部输出。这种顺序需要保证,既可以输出全部的排列,又不能重复输出某种排列,或者循环输出一部分排列。字典序就是用此种思想输出全排列的一种方式。这里以A{1,2,3,4}来说明用字典序输出全排列的方法。

首先,对于集合A的某种排列所形成的序列,字典序是比较序列大小的一种方式。以A{1,2,3,4}为例,其所形成的排列1234<1243,比较的方法是从前到后依次比较两个序列的对应元素,如果当前位置对应元素相同,则继续比较下一个位置,直到第一个元素不同的位置为止,元素值大的元素在字典序中就大于元素值小的元素。上面的a1[1...4]=1234和a2[1...4]=1243,对于i=1,i=2,两序列的对应元素相等,但是当i=2时,有a1[2]=3<a2[2]=4,所以1234<1243。

使用字典序输出全排列的思路是,首先输出字典序最小的排列,然后输出字典序次小的排列,……,最后输出字典序最大的排列。这里就涉及到一个问题,对于一个已知排列,如何求出其字典序中的下一个排列。这里给出算法。

  • 对于排列a[1...n],找到所有满足a[k]<a[k+1](0<k<n-1)的k的最大值,如果这样的k不存在,则说明当前排列已经是a的所有排列中字典序最大者,所有排列输出完毕。
  • 在a[k+1...n]中,寻找满足这样条件的元素l,使得在所有a[l]>a[k]的元素中,a[l]取得最小值。也就是说a[l]>a[k],但是小于所有其他大于a[k]的元素。
  • 交换a[l]与a[k].
  • 对于a[k+1...n],反转该区间内元素的顺序。也就是说a[k+1]与a[n]交换,a[k+2]与a[n-1]交换,……,这样就得到了a[1...n]在字典序中的下一个排列。

这里我们以排列a[1...8]=13876542为例,来解释一下上述算法。首先我们发现,1(38)76542,括号位置是第一处满足a[k]<a[k+1]的位置,此时k=2。所以我们在a[3...8]的区间内寻找比a[2]=3大的最小元素,找到a[7]=4满足条件,交换a[2]和a[7]得到新排列14876532,对于此排列的3~8区间,反转该区间的元素,将a[3]-a[8],a[4]-a[7],a[5]-a[6]分别交换,就得到了13876542字典序的下一个元素14235678。下面是该算法的实现代码

 

 1 /*
 2  * 将数组中的元素翻转
 3  */
 4 inline void Reverse(unsigned int* array, size_t array_size)
 5 {
 6     for(unsigned i = 0; 2 * i < array_size - 1; ++i)
 7     {
 8         unsigned int t = array[i];
 9         array[i] = array[array_size - 1 - i];
10         array[array_size - 1 - i] = t;
11     }
12 }
13 
14 inline int LexiNext(unsigned int* lexinum, size_t array_size)
15 {
16     unsigned int i, j, k, t;
17 
18     i = array_size - 2;
19 
20     while(i != UINT_MAX && lexinum[i] > lexinum[i + 1])
21     {
22         --i;
23     }
24 
25     //达到字典序最大值
26     if(i == UINT_MAX)
27     {
28         return 1;
29     }
30 
31     for(j = array_size - 1, k = UINT_MAX; j > i; --j)
32     {
33         if(lexinum[j] > lexinum[i])
34         {
35             if(k == UINT_MAX)
36             {
37                 k = j;
38             }
39             else
40             {
41                 if(lexinum[j] < lexinum[k])
42                 {
43                     k = j;
44                 }
45             }
46         }
47     }
48 
49     t = lexinum[i];
50     lexinum[i] = lexinum[k];
51     lexinum[k] = t;
52 
53     Reverse(lexinum + i + 1, array_size - i - 1);
54     return 0;
55 }
56 
57 /*
58  * 根据字典序输出排列
59  */
60 inline void ArrayPrint(const char* array, size_t array_size, const unsigned int* lexinum)
61 {
62     for(unsigned int i = 0; i < array_size; ++i)
63     {
64         cout << array[lexinum[i]] << ' ';
65     }
66 
67     cout << '\n';
68 }
69 
70 /*
71  * 基于逆序数的全排列输出
72  */
73 void FullArray(char* array, size_t array_size)
74 {
75     unsigned int lexinumber[array_size];
76 
77     for(unsigned int i = 0; i < array_size; ++i)
78     {
79         lexinumber[i] = i;
80     }
81 
82     ArrayPrint(array, array_size, lexinumber);
83 
84     while(!LexiNext(lexinumber, array_size))
85     {
86         ArrayPrint(array, array_size, lexinumber);
87     }
88 }

 

使用字典序输出集合的全排列需要注意,因为字典序涉及两个排列之间的比较,对于元素集合不方便比较的情况,可以将它们在数组中的索引作为元素,按照字典序生成索引的全排列,然后按照索引输出对应集合元素的排列,示例代码使用的就是此方法。对于集合A{a,b,c,d},可以对其索引1234进行全排列生成。这么做还有一个好处,就是对于字典序全排列生成算法,需要从字典序最小的排列开始才能够生成集合的所有排列,如果原始集合A中的元素不是有序的情况,字典序法将无法得到所有的排列结果,需要对原集合排序之后再执行生成算法,生成索引的全排列,避免了对原始集合的排序操作。

字典序算法还有一个优点,就是不受重复元素的影响。例如1224,交换中间的两个2,实际上得到的还是同一个排列,而字典序则是严格按照排列元素的大小关系来生成的。对于包含重复元素的输入集合,需要先将相同的元素放在一起,以集合A{a,d,b,c,d,b}为例,如果直接对其索引123456进行全排列,将不会得到想要的结果,这里将重复的元素放到相邻的位置,不同元素之间不一定有序,得到排列A'{a,d,d,b,b,c},然后将不同的元素,对应不同的索引值,生成索引排列122334,再执行全排列算法,即可得到最终结果。

 
 
Steinhaus-Johnson-Trotter算法是一种基于最小变换的全排列生成算法,对于排列a[1...n],该算法通过将a[i],与a[i-1](或a[i+1])进行交换,生成下一个排列,直到所有排列生成完毕为止,这样,当前排列与其后继排列只是两个相邻位置的元素发生了调换。当然,为了防止重复生成某一个排列,算法并非随意调换某两个元素之间的位置,其生成全排列的具体规则如下。
  • 首先,以字典序最小的排列起始,并且为该排列的每个元素赋予一个移动方向,初始所有元素的移动方向都向左。
  • 在排列中查找这样的元素,该元素按照其对应的移动方向移动,可以移动到一个合法位置,且移动方向的元素小于该元素,在所有满足条件的元素中,找到其中的最大者。
  • 将该元素与其移动方向所对应的元素交换位置。
  • 对于排列中,所有元素值大于该元素的元素,反转其移动方向。

这里有几个概念需要说明一下,所谓合法位置,是指该元素按照其移动方向移动,不会移动到排列数组之外,例如对于<4,<1,<2,<3,此时对于元素4,如果继续向左移动,就会超过数组范围,所以4的下一个移动位置是非法位置。而且,所有元素,都只能向比自己小的元素的方向移动,如上面例子中的元素2,3,而元素1是不能够移动到元素4的位置的。每次移动,都要对可以移动的所有元素中的最大者进行操作,上例中元素1,4不能移动,2,3都存在合法的移动方案,此时需要移动3,而不能移动2。合法移动之后,需要将所有大于移动元素的元素的移动方向反转,上例中的元素3移动后的结果是4>,1<,<3,<2,可以看到,元素4的移动方向改变了。再如此例子<2,<1,3>,4>,对于其中的元素2,4,其对应的下一个移动位置都是非法位置,而对于元素1,3,其下一个移动位置的元素,都比他们要大,对于该排列就找不到一个可以的移动方案,这说明该算法已经达到终态,全排列生成结束。下面是该算法的代码

 

 1 inline int SJTNext(unsigned int* index, size_t array_size, int* move)
 2 {
 3     unsigned int i, j, t;
 4 
 5     //找到最大合法移动的元素索引
 6     for(i = array_size - 1, j = array_size; i != UINT_MAX; --i)
 7     {
 8         if(i + move[i] < array_size && index[i] > index[i + move[i]])
 9         {
10             if(j == array_size)
11             {
12                 j = i;
13                 continue;
14             }
15 
16             if(index[i] > index[j])
17             {
18                 j = i;
19             }
20         }
21     }
22 
23     //未发现合法的移动策略
24     if(j == array_size)
25     {
26         return 1;
27     }
28 
29     t = index[j];//要交换位置的元素
30     i = j + move[j];//发生交换的位置
31     swap(index, i, j);
32     swap(move, i, j);
33 
34     //将所有比t大的元素的移动方向反转
35     for(i = 0; i < array_size; ++i)
36     {
37         if(index[i] > t)
38         {
39             move[i] = -move[i];
40         }
41     }
42 
43     return 0;
44 }
45 
46 /*
47  * 基于最小变换的Steinhaus–Johnson–Trotter算法
48  */
49 void FullArray(char* array, size_t array_size)
50 {
51     unsigned int index[array_size];
52     int move[array_size];
53 
54     for(unsigned int i = 0; i < array_size; ++i)
55     {
56         index[i] = i;
57         move[i] = -1;
58     }
59 
60     ArrayPrint(array, array_size, index);
61 
62     while(!SJTNext(index, array_size, move))
63     {
64         ArrayPrint(array, array_size, index);
65     }
66 }

 

代码使用了一个伴随数组move标记对应位置元素的移动方向,在元素移动时,move数组中的对应元素也要相应移动。该算法从初始排列<1,<2,<3,<4开始,可以生成4元素的所有排列,直至最终排列<2,<1,3>,4>为止,其状态转移如下图所示,该图片来自于Wiki百科。

实际上该算法是Shimon Even对于Steinhaus-Johnson-Trotter三人提出的全排列生成算法的改进算法,在算法中实际上还有一个问题需要解决,就是对于给定的排列,如何判断其所有元素的移动方向,如果上面所谓终态的移动方向是<2,<1,3>,<4,那么这个状态就还存在可行的移动方案。Johnson(1963)给出了判断当前排列各元素移动方向的方法,对于排列中的每个元素,判断所有比该元素小的元素所生成序列的逆序数,如果逆序数为偶,则该元素的移动方向为向左,否则移动方向向右,我们用这条原则来看一下上面的终态2,1,3,4。对于元素1,没有比1小的元素,此时我们认为,空序列的逆序数为偶,所以元素1的移动方向向左;对于元素2,比2小的元素形成的序列为1,单元素序列的逆序数为偶,所以2的移动方向向左;对于元素3,小于3的元素组成的序列为21,逆序数为1,奇数,所以3的移动方向向右;对于元素4,对应序列为213,逆序数为奇数,所以4的移动方向向右。根据该规则就可以知道,给定某一排列,其对应元素的移动方向是确定的。

 

 

基于阶乘数的全排列生成算法,是另一种通过序列顺序,输出全排列的算法。所谓阶乘数,实际上和我们常用的2进制,8进制,10进制,16进制一样,是一种数值的表示形式,所不同的是,上面这几种进制数,相邻位之间的进制是固定值,以10进制为例,第n位与第n+1位之间的进制是10,而阶乘数,相邻两位之间的进制是变值,第n位与第n+1位之间的进制是(n+1)!。对于10进制数,每一位的取值范围也是固定的0~9,而阶乘数每一位的取值范围为0~n。可以证明,任何一个数量,都可以由一个阶乘数唯一表示。下面以23为例,说明其在各种进制中的表现形式

 2进制8进制10进制16进制阶乘数
23101112723173210

 

其中10进制23所代表的数量的计算方法为

D(23) = 2×10^1 + 3×10^0 = 2×10 + 3×1 = 23

阶乘数3210所代表的数量的计算方法为

F(3210) = 3×3! + 2×2! + 1×1! + 0×0! = 3×6 + 2×2 + 1×1 + 1×0 = 23

对于阶乘数而言,由于阶乘的增长速度非常快,所以其可以表示的数值的范围随着位数的增长十分迅速,对于n位的阶乘数而言,其表示的范围从0~(n+1)!-1,总共(n+1)!个数。阶乘数有很多性质这里我们只介绍其和全排列相关的一些性质。

首先是加法操作,与普通十进制数的加法基本一样,所不同的是对于第n位F[n](最低位从第0位开始),如果F[n]+1>n,那么我们需要将F[n]置0,同时令F[n+1]+1,如果对于第n+1位,也导致进位,则向高位依次执行进位操作。这里我们看一下F(3210)+1,对于第0位,有F[0]+1=0+1=1>0,所以F[0]=0(实际上阶乘数的第0位一直是0),F[1]+1=1+1=2>1,F[1]=0,……,依次执行,各位都发生进位,最终结果F(3210)+1=F(10000)。

其次,对于n位的阶乘数,每一个阶乘数的各位的数值,正好对应了一个n排列各位的逆序关系。这里以abcd为例。例如F(2110),其对应的排列的意思是,对于排列的第一个元素,其后有两个元素比他小;第二个元素,后面有一个元素比他小;第三个元素,后面有一个元素比他小。最终根据F(2110)构建的排列为cbda。4位的阶乘数,与4排列的对应关系如下表所示。

0000abcd1000bacd2000cabd3000dabc
0010abdc1010badc2010cadb3010dacb
0100acbd1100bcad2100cbad3100dbac
0110acdb1110bcda2110cbda3110dbca
0200adbc1200bdac2200cdab3200dcab
0210adcb1210bdca2210cdba3210dcba

 

由此,我们就可以利用阶乘数与排列的对应关系构建集合的全排列,算法如下。

  • 对于n个元素的全排列,首先生成n位的阶乘数F[0...n-1],并令F[0...n-1]=0。
  • 每次对F[0...n-1]执行+1操作,所得结果,根据其与排列的逆序对应关系,生成排列。
  • 直到到达F[0...n-1]所能表示的最大数量n!-1为止,全部n!个排列生成完毕。

这里有一个问题需要解决,就是如何根据阶乘数,及其与排列逆序的对应关系生成对应的排列,这里给出一个方法,

  • 以字典序最小的排列a[0...n-1]作为起始,令i从0到n-2。
  • 如果F[i]=0,递增i。
  • 否则令t=a[i+F[i]],同时将a[i...i+F[i]-1]区间的元素,向后移动一位,然后令a[i]=t,递增i。

下面说明一下如何根据阶乘数F(2110)和初始排列abcd,构建对应的排列。首先,我们发现F[0]=2,所以我们要将a[0+2]位置的元素c放在a[0]位置,之前,先用临时变量t记录a[2]的值,然后将a[0...0+2-1]区间内的元素向后移动一位,然后令a[0]=t,得到cabd,i值增加1;然后有F[1]=1,所以我们要将a[1+1]=a[2]=b放在a[1]位置,同时将a[1]向后移动一位,得到排列cbad;然后有F[2]=1,所以将a[2+1]=a[3]=d放在a[2]位置,同时a[2]向后移动一位。最终得到cbda,排列生成结束。整个算法代码如下

 

 

inline int FacNumNext(unsigned int* facnum, size_t array_size)
{
    unsigned int i = 0;

    while(i < array_size)
    {
        if(facnum[i] + 1 <= i)
        {
            facnum[i] += 1;
            return 0;
        }
        else
        {
            facnum[i] = 0;
            ++i;
        }
    }

    return 1;
}

/*
 * 根据阶乘数所指定的逆序数根据原始字符串构建排列输出
 */
inline void BuildPerm(const char* array, size_t array_size, const unsigned int* facnum, char* out)
{
    char t;
    unsigned int i, j;

    memcpy(out, array, array_size * sizeof(char));

    for(i = 0; i < array_size - 1; ++i)
    {
        j = facnum[array_size - 1 - i];

        if(j != 0)
        {
            t = out[i + j];
            memmove(out + i + 1, out + i, j * sizeof(char));
            out[i] = t;
        }
    }
}

/*
 * 基于阶乘数(逆序数)的全排列生成算法
 */
void FullArray(char* array, size_t array_size)
{
    unsigned int facnum[array_size];
    char out[array_size];

    for(unsigned int i = 0; i < array_size; ++i)
    {
        facnum[i] = 0;
    }

    BuildPerm(array, array_size, facnum, out);

    for(unsigned int i = 0; i < array_size; ++i)
    {
        cout << out[i] << ' ';
    }

    cout << '\n';

    while(!FacNumNext(facnum, array_size))
    {
        BuildPerm(array, array_size, facnum, out);

        for(unsigned int i = 0; i < array_size; ++i)
        {
            cout << out[i] << ' ';
        }

        cout << '\n';
    }
}

 

 

用该算法生成1234全排列,顺序如下图,该图来自与Wiki百科。

从生成排列顺序的角度讲,概算法相较于字典序和最小变更有明显优势,但是在实际应用中,由于根据阶乘数所定义的逆序构建排列是一个O(n^2)时间复杂度的过程,所以算法的整体执行效率逊色不少。但是通过阶乘数建立逆序数与排列对应关系的思路,还是十分精彩的,值得借鉴

 

 

转载于:https://www.cnblogs.com/cswangchen/p/7469645.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值