写在前面:
本系列博客仅作为本人十一假期过于无聊的产物,对小学期的程序设计作业进行一个总结式的回顾,如果将来有BIT的学弟学妹们在百度搜思路时翻到了这一条博客,也希望它能对你产生一点帮助(当然,依经验来看,每年的题目也会有些许的不同,所以不能保证每一题都覆盖到,还请见谅)。
不过本人由于学艺不精,代码定有许多不足之处,欢迎各位一同来探讨。
同时请未来浏览这条博客的学弟学妹们注意,对于我给出完整代码的这些题,仅作帮助大家理解思路所用(当然,因为懒,所以大部分题我都只给一个伪代码)。Anyway,请勿直接复制黏贴代码,小学期的作业也是要查重的,一旦被查到代码重复会严厉扣分,最好的方法是浏览一遍代码并且掌握相关的要领后自己手打一遍,同时也要做好总结和回顾的工作,这样才能高效地提升自己的代码水平。
加油!
成绩 | 10 | 开启时间 | 2021年08月27日 星期五 12:00 |
折扣 | 0.8 | 折扣时间 | 2021年09月3日 星期五 23:00 |
允许迟交 | 否 | 关闭时间 | 2021年10月10日 星期日 23:00 |
Background
在古代中国,《三字经》、《百家姓》、《千字文》被合称为三、百、千,都是非常重要的启蒙教育课本,广为流传。而其中问世最早的《千字文》更凭借其优美的文字、华丽的辞藻成为中华传统文化的一个重要组成部分,得到了人们的普遍重视和喜爱。
《千字文》的作者,是梁武帝时代官拜散骑员外郎的周兴嗣。历来在正史上的记载,就这样一笔带过,但据私家笔记的野史记载,内容不是这样简单了。周兴嗣同梁武帝本来便是文字之交的朋友,在萧齐时代,还在朝廷上有过同僚之谊。到了梁武帝当了皇帝,那就变成君臣的关系。由朋友变君臣,说是关系不错,其实,伴君如伴虎,反是最糟糕的事,周兴嗣有一次不小心得罪了梁武帝,梁武帝一怒之下,想杀他或很严厉地处分他,到底还是于心不忍,只好下令把先关起来再说。但梁武帝又说了一句话,你不是文才很好吗?你能在一夜之间,把一千个不同的字,写一篇好文章,就赦你无罪。因此,周兴嗣就在一夜之间,挖空心思,写了这篇《千字文》。文章写好了,可是在一夜之间,头发、眉毛、胡子也都白了!大家要注意,用一千个不同的中文字,一夜之间,写出有关宇宙、物理、人情、世故的文间,等于写了一篇非常精简的“中国文化纲领要点”,虽然,只写到南北时期的梁朝为止,实在也太难了。梁武帝本人,才华文学都自命不凡,看了周兴嗣一夜之间之间所写的《千字文》,也不能不佩服。周兴嗣因此得到宽恕,而且还特加赏赐。 ——摘自南怀瑾《原本大学微言》
现代人,一生中有机会通读千字文的机会是非常少的,很多理工科的大学生,完全没有听说过这篇神奇的文章,自然也会抱着“怀疑一切”的态度问,真的有这么神吗,真的一个重复的字都没有吗?耳听为虚,眼见为实,我们就来检验一下千字文中是不是真的没有重复的字。
不过在动手之前,先让我们花上几分钟的时间,读读《千字文》吧:简体中文版、正体中文版。
Description
请编写一个程序,从输入中读取一篇中文文章,并统计出该文章中 ASCII 字符以外的重复出现的每一个字重复出现的次数。
文章使用 UTF-8 编码,可能会出现任何可以用 UTF-8 编码表示的字符(不限于中文)。
文章中所有的字符在 UCS-2 能够表示的范围内,即字符的 Unicode 值用两个字节就可以表示。
Input
一篇文章,总字数不限、每行字符数不限。每个字重复出现的次数不超过 6e4次。
Output
按照 Unicode 编码从小到大的顺序,输出文章中 ASCII(0~127)字符以外的每一个重复出现过的字重复出现的次数。每行包含三项内容,首先是重复的字符(以 UTF-8 编码输出),然后是该字符的 Unicode 编码值(十六进制输出,字母均使用小写,长度不足4位数的用0补齐),最后输出该字符的重复次数。
如果文章中没有出现重复的字,则输出No repeat!
。
测试用例 1 | 以文本方式显示
| 以文本方式显示
| 1秒 | 1024KB |
测试用例 2 | 以文本方式显示
| 以文本方式显示
| 1秒 | 1024KB |
题意分析:
回想一下,这应该是第一道把我折磨的够呛的题。
先建议各位补充一下有关字符编码的基础知识(名词解释:Unicode,UTF-8),不然这题是无从下手的。其次的话,可能有些人的编译器不支持中文的输入(例如万恶的DEVCPP和CLion),这里可以借助洛谷的洛谷在线IDE来用一用,当然也可以采用文件输入的方法(指路c++中ifstream及ofstream超详细说明_cpp加油站的专栏-CSDN博客)。
好,说回正题,相信这应该是绝大多数同学第一次处理中文输入,那么要怎么来处理呢?
读完上面有关字符编码介绍的同学可以知道(没读的还不快去读!),任何字符在内存中的存储都是以字节为单位,如果某字节以0开头,那么就代表是ASCII编码,且字符长度为1字节;如果某字节以1开头,那就是我们今天要谈的了:
形如 110XXXXX的字节,后面会跟一个10XXXXXX的字节,这里的可变位就有11位,代表了两字节能够处理的字符,把这所有的X提取出来排成一列再查Unicode表就可以知道他对应的字符;而形如 1110XXXX的字节后面会跟两个10XXXXXX的字节,这里的可变位就有16位。十六位可变位也就是能表示0000到FFFF的所有Unicode编码,对这题已经足够适用了(因为题目提示了所有文字均在Unicode编码的两个字节,即四个十六进制位内)。
所以问题的关键的关键就在于,如何对字符进行正确的分类,以及如何提取这些有效的可变位X。
对字符进行正确分类,我们需要用到一个以前很少用的数据类型:unsigned char(当然,此处用char完全没问题,只是我不想处理那该死的负号和反码补码关系)。所谓unsigned char,就是直接把内存的一个字节当做一个八位的二进制数来读取,举个例子,如果内存中的某个字节是01011011,用unsigned char类型的变量读取出来就是91(1+2+8+16+64)。可见,用unsigned char类型的变量保存字节是非常方便的。
于是我们可以对字符进行一个初步的划分,利用getchar读取字符的首字节存入一个unsigned char变量,如果对应的值是255,即二进制编码为11111111,这就是EOF的宏定义,直接跳出;如果在[0,127]区间内,也就是00000000到01111111,这一块归ASCII码管,且后续不需要再读入字节;如果在[192,223]内,也就是11000000到11011111,这一块属于两字节长的Unicode字符,所以我们需要额外用getchar读进来一个字符再进行处理;如果在[224,239]内,也就是11100000到11101111,这一块属于三字节长的Unicode字符,所以我们需要额外用getchar读进来两个字符再处理。
对字符的划分结束之后,我们就要想着如何提取这些有效的X了(因为题目要求我们输出Unicode编码)。还记得前几题我们提到的位运算吗?今天他又来了(没看的记得去复习!!)。我们可以很轻松地用移位操作来完成这个提取的工作:对于二字节字符,假设两个字节为a和b,我们需要做以清除第一个字节开头的“110”,做
以清除第二个字节开头的“10”,这时候用一个unsigned int c = (a << 6) + b,得到的C就是对应字符的Unicode码的十进制值,我们只要输出的时候以十六进制输出就可以了。相对的,如果是三字节字符,我们则需要对后面两个字节都清除开头的“10”,其他步骤大同小异,不再赘述。
当然,如果不用位运算,我们也有解决方法。我们可以运用二进制运算的特性,做,最终呈现出来的效果是完全一样的,只是这个的速度不如位运算来得快,代码也不如位运算来的直观。
贴代码:
#include <bits/stdc++.h>
using namespace std;
struct character{
bool stored = false;
unsigned char s[3]; //其实这个操作比较多余,可以在输出的时候把Unicode还原回去就行,但我比较懒就保存了,这样可以少写几行,追求极致效率的同学可以挑战一下不写这一行
int count = 0;
}c[65536];
//开成0~65535是因为FFFF是65535,这样可以直接用Unicode码当做下标,就可以做到从小到大输出
int main(){
unsigned char temp;
unsigned int val;
while((temp = getchar()) != 255){
if(temp >= 0 and temp <= 127) //0xxxxxxx ascii
continue;
else if(temp >= 192 and temp <= 223){ //110xxxxx 10xxxxxx 2bytes
character tmp;
tmp.s[0] = temp;
tmp.s[1] = getchar();
val = ((tmp.s[0] - 192) << 6) + (tmp.s[1] - 128);
//或者写成val = ((tmp.s[0] - 192) * 64) + (tmp.s[1] - 128);
if(not c[val].stored){
c[val].s[0] = tmp.s[0];
c[val].s[1] = tmp.s[1];
c[val].stored = true;
}
c[val].count ++;
}
else if(temp >= 224 and temp <= 239){ //1110xxxx 10xxxxxx 10xxxxxx 3bytes
character tmp;
tmp.s[0] = temp;
tmp.s[1] = getchar();
tmp.s[2] = getchar();
val = ((tmp.s[0] - 224) <<12) + ((tmp.s[1] - 128) <<6) + (tmp.s[2] - 128);
//或者写成val = ((tmp.s[0] - 224) * 4096) + ((tmp.s[1] - 128) *64) + (tmp.s[2] - 128);
if(not c[val].stored){
c[val].s[0] = tmp.s[0];
c[val].s[1] = tmp.s[1];
c[val].s[2] = tmp.s[2];
c[val].stored = true;
}
c[val].count ++;
}
}
int flag = 1;
for(int i = 0; i <= 65536; i++){
if(c[i].count >= 2){
flag = 0;
printf("%s 0x%04x %d\n", c[i].s, i, c[i].count);
}
}
if(flag){
printf("No repeat!\n");
}
return 0;
}