编程珠玑第15章

一、单词
(1)为文档中包含的单词生成一个列表?
解答:
    方法一:用到标准模板库中的sets和strings

  1. #include <iostream>
  2. #include <set>
  3. #include <string>

  4. using namespace std;

  5. int main(int argc, char **argv)
  6. {
  7.     set<string> str;
  8.     set<string>::iterator iter;
  9.     string t;
  10.     while(cin >> t)
  11.     {
  12.         str.insert(t);
  13.     }
  14.     for(iter = str.begin(); iter != str.end(); iter++)
  15.     {
  16.         cout << *iter << endl;
  17.     }
  18.     return 0;
  19. }
    while循环读取输入并将每个单词插入集合str(忽略重复的单词 ),然后for循环迭代整个集合,并按排好的顺序输出单词。

(2)为单词进行计数
    方法一:用标准模板库中的map将整个计数与每个字符串联系起来
  1. #include <iostream>
  2. #include <map>
  3. #include <string>

  4. using namespace std;

  5. int main(int argc, char **argv)
  6. {
  7.     map<string, int> m;
  8.     map<string, int>::iterator iter;
  9.     string t;
  10.     while(cin >> t)
  11.     {
  12.         m[t]++;
  13.     }
  14.     for(iter = m.begin(); iter != m.end(); iter++)
  15.     {
  16.         cout << iter->first << " " << iter->second << endl;
  17.     }
  18.     return 0;
  19. }
     方法二:为了减少处理时间,可以自己建立散列表

    为了减少处理时间,可以建立散列表。其中内存分配函数malloc 被改为自定义更高效的 nmalloc和 smalloc

[html]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4.   
  5. typedef struct node *nodeptr;  
  6. typedef struct node {  
  7.     char *word; //单词   
  8.     int count;  //单词个数   
  9.     nodeptr next;  
  10. } node;  
  11. #define NHASH 29989/*圣经中共29131个单词,用跟29131最接近的质数作为散列表大小*/  
  12. #define MULT 31    /*乘数*/  
  13. nodeptr bin[NHASH];//散列表   
  14.   
  15. unsigned int hash(char *p)//哈希函数,将每个字符串映射成小于NHASH的正整数   
  16. {   unsigned int h = 0;  
  17.     for ( ; *p; p++)  
  18.         h = MULT * h + *p;  
  19.     return h % NHASH;  
  20. }  
  21.   
  22. #define NODEGROUP 1000  
  23. int nodesleft = 0;  
  24. nodeptr freenode;  
  25.   
  26. nodeptr nmalloc()  
  27. {   if (nodesleft == 0) {  
  28.         freenode = malloc(NODEGROUP*sizeof(node));  
  29.         nodesleft = NODEGROUP;  
  30.     }  
  31.     nodesleft--;  
  32.     return freenode++;  
  33. }  
  34.   
  35. #define CHARGROUP 10000  
  36. int charsleft = 0;  
  37. char *freechar;  
  38.   
  39. char *smalloc(int n)  
  40. {   if (charsleft < n) {  
  41.         freechar = malloc(n+CHARGROUP);  
  42.         charsleft = n+CHARGROUP;  
  43.     }  
  44.     charsleft -n;  
  45.     freechar += n;  
  46.     return freechar - n;  
  47. }  
  48.   
  49. void incword(char *s)//增加与单词相关联的计数器的值,如果之前没有这个词,对计数器初始化   
  50. {   nodeptr p;  
  51.     int h = hash(s);//找到与单词对应的箱   
  52.     for (p = bin[h]; p != NULL; p = p->next)  
  53.         if (strcmp(s, p->word) == 0) {//该箱子中若有 该单词,则对应count++ ,否则新建单词指针 (采取头插法)   
  54.             (p->count)++;  
  55.             return;  
  56.         }  
  57.     p = nmalloc();//本来用malloc就可以,但优化成了nmalloc   
  58.     p->count = 1;  
  59.     p->word = smalloc(strlen(s)+1);//本来用malloc就可以,但优化成了smalloc   
  60.     strcpy(p->word, s);  
  61.     p->next = bin[h];  
  62.     bin[h] = p;  
  63. }  
  64.   
  65. int main()  
  66. {   int i;  
  67.     nodeptr p;  
  68.     char buf[100];  
  69.     for (i = 0; i < NHASH; i++)//将每个箱初始化   
  70.         bin[i] = NULL;  
  71.     while (scanf("%s", buf) != EOF)  
  72.         incword(buf);//增加与输入单词相关联的计数器的值   
  73.     for (i = 0; i < NHASH; i++)//输出每一个不等于NULL的箱的字符串和个数   
  74.         for (p = bin[i]; p != NULL; p = p->next)  
  75.             printf("%s %d\n", p->word, p->count);  
  76.     return 0;  
  77. }<span style="font-size:18px;"><span style="font-size:18px;">  
  78. </span></span>  
       特别注意:以上为C程序,不是C++

二、短语
    给定一个文本文件作为输入,插在其中最长的重复子字符串。例如,“Ask not what your country can do for you, but what you can do for your country”中最长的重复字符串是“can do for you”,第二长的是“your country”。

方法一:双重for循环依次比较每个字符串,找到最长重复子字符串

  1. #include <stdlib.h>
  2. #include <string.h>
  3. #include <stdio.h>

  4. //找到两个字符串公共部分的长度
  5. int comlen(char *p, char *q)
  6. {    
  7.     int i = 0;
  8.     while (*&& (*p++ == *q++))
  9.     {
  10.         i++;
  11.     }
  12.     return i;
  13. }

  14. int main()
  15. { 
  16.     int i, j;
  17.     int maxi, maxj;
  18.     int currentlen, maxlen = -1;
  19.     char *str = "ask not what your country can do for you, but what you can do for your country";
  20.     int length = strlen(str);

  21.     for(= 0; i < length; i++)
  22.     {
  23.         for(= i + 1; j < length; j++)
  24.         {
  25.             currentlen = comlen(str + i, str + j);
  26.             if(currentlen > maxlen)
  27.             {
  28.                 maxlen = currentlen;
  29.                 maxi = i;//i标记了最长公共子串开始的位置
  30.                 maxj = j;
  31.             }
  32.         }
  33.     }

  34.     for(= 0; i < maxlen; i++)
  35.     {
  36.         printf("%c", str[maxi + i]);
  37.     }
  38.     printf("n");
  39.     return 0;
  40. }

方法二:采用后缀数组来处理该问题
    我们的程序最多处理MAXN个字符,这些字符存储在数组c中。
    #define MAXN  50000
    char c[MAXN], *a[MAXN];
    我们使用一个称为“后缀数组”的数据结构,这个结构是一个字符指针数组,记为a。读取输入时,我们对a进行初始化,使得每个元素指向输入字符串中的相应字符:
    while(ch  = getchar() != EOF)
    {
            a[n] = &c[n];
            c[n] = ch;
    }
    c[n] = 0;

    元素a[0]指向整个字符串,下一个元素指向从第二个字符开始的数组后缀,等等。对于输入字符串“banana”,该数组能够表示如下后缀:
                            char *a="banana"
                            a[0]=banana
                            a[1]=anana
                            a[2]=nana
                            a[3]=ana
                            a[4]=na
                            a[5]=a
    如果某个长字符串在数组c中出现了两次,那么它将出现在两个不同的后缀中,因此我们队数组进行排序以寻找相同的后缀。“banana”数组排序为:
                            a[0]=a
                            a[1]=ana
                            a[2]=anana
                            a[3]=banana
                            a[4]=na
                            a[5]=nana
    然后就可以扫描数组,通过比较相邻元素来找出最长的重复字符串。
    该方法由于排序的存在,时间复杂度为O(nlogn)。

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

//只能比较字符串,两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇''为止。
int pstrcmp(const void *p, const void *q)
{
	return strcmp(*(char **)p, *(char **)q);
}
int comlen(char *p, char *q)//返回两个参数共同部分的长度 
{    int i = 0;
while (*p && (*p++ == *q++))
	i++;
return i;
}

#define M 1
#define MAXN 5000000
char c[MAXN], *a[MAXN];

int main()
{ 
	int i, ch, n = 0, maxi, maxlen = -1;
	while ((ch = getchar()) != EOF) {
		a[n] = &c[n];
		c[n++] = ch;
	}
	c[n] = 0;
	qsort(a, n, sizeof(char *), pstrcmp);//快速排序 
	for (i = 0; i <n-M; i++)
          {
		if (comlen(a[i], a[i+M]) > maxlen) //比较相邻字符串相同个数 
		{
			maxlen = comlen(a[i], a[i+M]);
			maxi = i;
		}
            }
		printf("%.*s\n", maxlen, a[maxi]);
		return 0;
}
上面函数要注意下面几点:

1: qsort函数对于字符串进行排序时compare函数的写法
  qsort是万能数组排序函数,必须要学会使用,简单的数组自然不用说,这里主要讨论一下字符串数组的使用。

  首先看一下qsort的原型:
     void qsort(void *base, size_t nmemb, size_t size, 
int(*compar)(const void *, const void *));
  正确使用这个函数要注意几点:
1.base要传数组的首地址
2.size传的是每个元素的大小
3.正确编写compar函数

下面是实际应用:
    一个字符串数组:*str[MAX],假设里面现在保存了n个字符串了。
    首先要正确理解什么是字符串数组,简单的说,可以理解成它就是一个数组,只不过其中的元素是一串字符串,而访问这些字符串,得用指针,也就是它们的地址,比如*name[]={"james","henry"},那么访问其中的字符串就是name[0],name[1]...这里就有个容易混淆的地方了,对于字符串数组,那么每个元素的大小到底是多少呢?对name[0]来说,到底是字符串“james”的长度5,还是char*的大小4呢?答案应该是4,因为字符串数组里面保存的是各个字符串的指针,所以回到上面所说的第二点注意,用qsort的时候应该要传sizeof(char *);
   第二,编写compar函数比较字符串有地方要注意:
       不能把strcmp本身传给qsort,即不能写strcmp(p,q),因为形参是const void*类型,同理,写成strcmp((char *)p, (char *)q);也是无效的;正确的写法应该是:strcmp(*(char **)p, *(char **)q);先强制转换成char**,在用*减少一层间接寻址操作:
      int compar_words(const void *p, const void *q)
      {
return strcmp(*(char **)p, *(char **)q);
      }
大家可以好好体会一下。对于上面的应用,最后使用qsort应该是这样:
qsort(str, n, sizeof(char *), compar);

2 printf函数输出指定长度的字符串

1. 原样输出字符串:
    printf("%s", str);

2. 输出指定长度的字符串, 超长时不截断, 不足时右对齐:
    printf("%Ns", str);             --N 为指定长度的10进制数值

3. 输出指定长度的字符串, 超长时不截断, 不足时左对齐:
    printf("%-Ns", str);            --N 为指定长度的10进制数值

4. 输出指定长度的字符串, 超长时截断, 不足时右对齐:
    printf("%N.Ms", str);           --N 为最终的字符串输出长度
                                    --M 为从参数字符串中取出的子串长度

5. 输出指定长度的字符串, 超长时截断, 不足时左对齐是:
    printf("%-N.Ms", str);          --N 为最终的字符串输出长度
                                    --M 为从参数字符串中取出的子串长度

注意,所谓超长时截断用到的M并不是只在超长时才起作用,而是不管你有没有超长,都必须截取这么长。所以
printf("%-5.2", "123")的输出为:
12空格空格空格
只截取了2个字符,其他的用空格填补,而且左对齐。

6. 上述N,M是可以动态指定的,方法是用*代替M或者N,然后在参数列表里加上一个数字参数。例子:
1)printf("%-*.*s", 5,2,"123");与上面的例子效果一样。

      前边*定义的是总的宽度,后边*是指定输出字符个数。分别对应外边参数m和n。

2)printf("%.*s", 2,"123");也可以省略掉前面的那个*号。
3)printf("%*s", 5, "123");表示输出长度为5, 如果超长也不截断,不够的话填补,右对齐。


 ★d格式符,用来输出十进制整数.

                ⑴%d,按整型数据的实际长度输出.

                ⑵%md,m为指定的输出字段的宽度,数据位数小于m,左边补空格,若大于m,按实际长度输出

                ⑶%ld,输出长整型数据(long)

       ★o格式符,以八进制输出整数(不带符号,他将符号位也作为八进制数的一部分了)
                ⑴%o,参考%d的解释.

                ⑵%lo,参考%ld的解释.

                ⑶%mo,参考%md的解释.

        ★x,X格式符,以十六进制输出整数
                也是3种参考%d的解释.
        ★u格式符,用来将unsigned型数据,既无符号数,以十进制形式输出
        ★c格式符,输出一个字符.
        ★s格式符,输出一个字符串.
                ⑴%s,如printf("%s","CHINA")

                ⑵%ms,输出的字符串占m列,字符串长度小于m,左边补空格,如果超出则全部输出.

                ⑶%-ms,串小于m,则在m列范围内字符串左靠,右补空格.

                ⑷%m.ns,输出占m列,但只取字符串左端n个字符.这n个字符输出在m列的右边,然后左边补空格.

                ⑸%-m.ns,和上面的放下,就是n个字符输出在m列的左侧,右边补空格.n>m,那么m自动取n的值,既保证n个字符正常输出.

               printf("%3s,%7.2s,%.4s,%-5.3s\n","CHINA","CHINA","CHINA","CHINA");

        ★f格式符,用来输出实数,以小数形式输出.

                ⑴%f,全部输出,而且输出6位小数.

                ⑵%m.nf,输出数据共占m列,n位小数,如果数据长度小于m那么左边补空格

                ⑶%-m.nf,和上面的m.nf相反,为左靠齐,右补空格.

        ★e,E格式符,以指数形式输出实数

                ⑴%e,不指定输出数据所占的宽度和数字部分的小数位数.

                ⑵%m.ne和%-m.ne,这里n指小数部分的位数

        ★g,G格式符,用来输出实数,它根据数值大小,自动选择f格式还是e格式,(选占宽最少的一种),且不输出无意义的0.这种格式用的不多. 


对于上面程序的理解

后缀数组举例 如下目标字符串: banana 其长度为6,则后缀数组的长度为6,分别是以b开头的字串(长度为6),以a开头的字串(长度为5),以n开头的字串(长度为4)。。。最后一个是以a开头的字串(长度为1)。
后缀[0] banana
后缀[1] anana
后缀[2] nana
后缀[3] ana
后缀[4] na
后缀[5] a

回到正题,查找一段文字中最长的重复字串。(注意:这不同于算法设计课中常讲的两个字符串的最长公共子序列问题(LCS),LCS问题的最长公共字串可以不是连续的)
最朴素的算法是,让后缀数组之间两两比较,找出最长的公共字串(注意,这里的最长的公共字串必须是以首字符参与匹配的,如果首字母都不匹配,那么长度为0,eg后缀[0]和后缀[1]之间的首字母不匹配,则两者的最长公共字串长度为0.。),但是时间复杂度为O(n^2).


该思想基于以下两个信息:
1)如果存在一个最长的重复字串,那么两个字串均是来自文本串不同的后缀,但这两个后缀有相同的前缀!(这个前缀也就是重复字串了)
2)既然最终结果的后缀肯定拥有相同的前缀,那么我就没有必要让全部后缀之间两两比较,而仅仅比较具有相同的前缀的后缀即可!这可以大大的减少比较的次数,提高效率。

所以,算法的流程是,先将后缀数组字母排序,然后顺次比较(避免了两两比较)即可。
后缀[0] a
后缀[1] ana
后缀[2] anana
后缀[3] banana
后缀[4] na
后缀[5] nana

最终的比较结果是 后缀[1] 和 后缀[2] 之间存在最长公共字串 ana。

后记:
已经多次领教了从后往前寻找算法的优势,EG BM字符串匹配算法,Sunday算法等。这又是一例!

编程珠玑的最后习题部分给出了另外一个问题,如何找到两个不同的字符串中的最长连续字串?
编程珠玑同样利用后缀数组给出了解答,不过答案我看不太明白。

还有一道,如何找到出现次数超过M次的最长连续字串。(M>=2, 当M==2时,就相当于找最长重复字串)
答案是 
当M=2时,最长重复字串问题,我们使用的函数是 comlen(a[i], a[i+1]);
当M>2时,我们就需要使用 comlen(a[i], a[i+M-1]);
eg M=3时, 仍按照上面的例子,求出的最长字串为 "a“,长度为1; 因为a[i] 和 a[i+M-1]的最长重复字串肯定在a[i+1]~a[i+M-2]之间也重复了,也就是至少重复了M次。
本例中 a[0] 和 a[2]的重复字串 "a" 在a[1]中也重复了。
后缀[0] a
后缀[1] ana
后缀[2] anana
后缀[3] banana
后缀[4] na
后缀[5] nana

(注意:该问题等价于找一个字符串中的最长回文串)
(该地址给出了一个利用后缀数的解答:http://blog.csdn.net/g9yuayon/archive/2008/06/21/2574781.aspx
该作者的主要思路就是:分别把一个字符串建立一个后缀树,然后把字符串的对应的倒序字符串再建立一个后缀树,把两个后缀树合并为一个后缀树。判断对应的节点的公共最长前缀。(也就是是找两个结点的最近的祖先结点,祖先结点对应的字符串长度即是回文串的半径!)
建立后缀树的常用技巧是在后缀之后添加结束符。这样可以压缩存储后缀树。如果对两个不同的后缀树的结束符不一样,就可以合并两个不同的后缀树。

广义的后缀树是两个后缀树的直接合并(两个树采用相同的结束符)。这些知识在上面的链接里面都有。

后缀数组和后缀树有何联系?
后缀数和Trie树有何联系?
其实后缀数组本身用处不大,经过排序后用处就大了,比如可以解决上面提到的查找出现超多M次的最长字串问题。
把后缀数组代表的字符串按照压缩存储为trie数,就得到了后缀树!

如果将一个词典典存储为Trie树,那么可以快速的查找某个指定的单词是否在词典中。
如果将一系列单词(比如一篇文章中的所有单词)存储为Trie树,那么可以快速的查找某个指定的单词是否在文章中出现过!进一步可以查找出现过多少次!(通过统计其属于其得叶子节点数)
Trie树可用来查找任意字串是否在


三、生成随机文本
    基于字母:下一个字母设置为前一个字母的随机函数,或者下一个字母设置为前k个字母的随机函数。

  1. /* Copyright (C) 2000 Lucent Technologies */
  2. /* Modified from markov.in 'Programming Pearls' by Jon Bentley */

  3. /* markovlet.-- generate letter-level random text from input text
  4.     Alg: Store text in an array on input
  5.          Scan complete text for each output character
  6.             (Randomly select one matching k-gram)
  7.  */

  8. #include <stdio.h>
  9. #include <stdlib.h>

  10. char x[5000000];

  11. int main()
  12. {    int c, i, eqsofar, max, n = 0, k = 5;
  13.     char *p, *nextp, *q;
  14.     while ((= getchar()) != EOF)
  15.     {
  16.         x[n++] = c;
  17.     }
  18.     x[n] = 0;
  19.     p = x;
  20.     srand(1);
  21.     for (max = 2000; max > 0; max--) 
  22.     {
  23.         eqsofar = 0;
  24.         for (= x; q < x + n - k + 1; q++) 
  25.         {
  26.             for (= 0; i < k && *(p+i) == *(q+i); i++)
  27.                 ;
  28.             if (== k)
  29.             {
  30.                 if (rand() % ++eqsofar == 0)
  31.                 {
  32.                     nextp = q;
  33.                 }
  34.             }
  35.         }
  36.         c = *(nextp+k);
  37.         if (== 0)
  38.         {
  39.             break;
  40.         }
  41.         putchar(c);
  42.         p = nextp+1;
  43.     }
  44.     return 0;
  45. }

    基于单词:
    方法一:最笨的方法是 随机输出字典中的单词;
    方法二:稍微好点的方法是读取一个文档,对每个单词进行计数,然后根据适当的概率选择下一个输出的单词;
    方法三:如果使用在生成下一个单词时考虑前面几个单词的马尔科夫链,可以得到更加令人感兴趣的文本;
    下面是香农的算法:以构建【字母级别的一阶文本 】为例,随机打开一本书并在该页随机选择一个字母记录下来。然后翻到另一页开始读,直到遇到该字母,此时记录喜爱其后面的那个字母。再翻到另外一页搜索上述第二个字母并记录其后面的那个字母。依次类推。对于【字母级别的1阶、2阶文本和单词级别的0阶、1阶文本 】,处理过程类似。
  1. /* Copyright (C) 1999 Lucent Technologies */
  2. /* From 'Programming Pearls' by Jon Bentley */

  3. /* markov.-- generate random text from input document */

  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>

  7. char inputchars[4300000];
  8. char *word[800000];
  9. int nword = 0;
  10. int k = 2;

  11. int wordncmp(char *p, char* q)
  12. {    
  13.     int n = k;
  14.     for ( ; *== *q; p++, q++)
  15.     {
  16.         if (*== 0 && --== 0)
  17.             return 0;
  18.     }
  19.     return *- *q;
  20. }

  21. int sortcmp(char **p, char **q)
  22. {    
  23.     return wordncmp(*p, *q);
  24. }

  25. char *skip(char *p, int n)
  26. {    for ( ; n > 0; p++)
  27.     {
  28.         if (*== 0)
  29.             n--;
  30.     }    
  31.     return p;
  32. }

  33. int main()
  34. {    
  35.     int i, wordsleft = 10000, l, m, u;
  36.     char *phrase, *p;
  37.     
  38.     word[0] = inputchars;
  39.     while (scanf("%s", word[nword]) != EOF) 
  40.     {
  41.         word[nword+1] = word[nword] + strlen(word[nword]) + 1;
  42.         nword++;
  43.     }

  44.     for (= 0; i < k; i++)
  45.         word[nword][i] = 0;
  46.     for (= 0; i < k; i++)
  47.         printf("%sn", word[i]);

  48.     qsort(word, nword, sizeof(word[0]), sortcmp);

  49.     phrase = inputchars;
  50.     for ( ; wordsleft > 0; wordsleft--) 
  51.     {
  52.         l = -1;
  53.         u = nword;
  54.         while (l+!= u) 
  55.         {
  56.             m = (+ u) / 2;
  57.             if (wordncmp(word[m], phrase) < 0)
  58.                 l = m;
  59.             else
  60.                 u = m;
  61.         }
  62.         for (= 0; wordncmp(phrase, word[u+i]) == 0; i++)
  63.         {
  64.             if (rand() % (i+1) == 0)
  65.             {
  66.                 p = word[u+i];
  67.             }
  68.         }
  69.         phrase = skip(p, 1);
  70.         if (strlen(skip(phrase, k-1)) == 0)
  71.         {
  72.             break;
  73.         }
  74.         printf("%sn", skip(phrase, k-1));
  75.     }
  76.     return 0;
  77. }

课后习题

习题8

找出出现超过M次的最长的字符串。后缀数组a中a[i]~a[i+M]表示的就是M+1个字符串,找出其中这M+1个字符串共有的部分就是超过M次的字符串,同时还要找最长的。而且,这个超过M次的字符串一定出现在最后一个字符串中。否则就不满足条件了。因为快速排序后后缀数组是有序的,我们可以通调用在第一个和最后一个字符串上调用comlen函数来快速的确定这M+1个字符串中共有的字符数。

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

//只能比较字符串,两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇''为止。
int pstrcmp(const void *p, const void *q)
{
	return strcmp(*(char **)p, *(char **)q);
}
int comlen(char *p, char *q)//返回两个参数共同部分的长度 
{    int i = 0;
while (*p && (*p++ == *q++))
	i++;
return i;
}

#define M 1
#define MAXN 5000000
char c[MAXN], *a[MAXN];

int main()
{ 
	int i, ch, n = 0, maxi, maxlen = -1;
	while ((ch = getchar()) != EOF) {
		a[n] = &c[n];
		c[n++] = ch;
	}
	c[n] = 0;
	qsort(a, n, sizeof(char *), pstrcmp);//快速排序 
	for (i = 0; i <n-M; i++)
		if (comlen(a[i], a[i+M]) > maxlen) //比较相邻字符串相同个数 
		{
			maxlen = comlen(a[i], a[i+M]);
			maxi = i;
		}
		printf("%.*s\n", maxlen, a[maxi]);
		return 0;
}

习题 9

具体见http://blog.csdn.net/wordwarwordwar/article/details/41490013





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值