版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/richenyunqi/article/details/89206423
欢迎访问我的CCF认证考试题解目录哦 https://blog.csdn.net/richenyunqi/article/details/83385502
题目描述
算法设计
这道题是我在csp上见过的第3题中测试数据量最大的一道题。为了能AC这道题我们需要解决3个问题:
- 读取数据
由于磁盘数量最大为 ,每块磁盘最大容量为 ,一个字节需要用2个16进制字符来表示,所以一块磁盘最大需要约 个字符来表示,故输入的所有磁盘数据最多有 个字符,这个数字达到了 亿个字符级别。我编写好算法后,使用了各种优化方法都只能拿到70分,最后3个测试点总是过不去,后来才发现是数据量太大,读取数据的时候就已经超时了。为此我尝试了C语言中scanf、gets、fgets,C++中cin、getline这5个函数去读取字符串,并编写了博客C语言中scanf、gets、fgets,C++中cin、getline读取字符串的效率比较。最终使用了读取效率最高的fgets
函数,才最终得以AC。所以我也建议读者使用fgets
函数去读取输入数据。 - 根据输入的块号,确定该块位于那个磁盘的第几块
以下涉及到的所有编号都从0开始。
首先说一下磁盘上条带号是如何排布的,如下图,图中编号为条带号:
有了输入的块号,以下直接给出计算该块所在磁盘号和所在磁盘上的块号的公式(%代表取模运算):
- 如何对两个16进制字符串进行异或运算
有两种方法:
- 先将16进制字符串转化为10进制数,进行异或运算之后再转换回16进制字符。这种方法可以使用C语言中的
%X
参数进行输入和输出,以及sscanf
函数和sprintf
函数进行10进制数和16进制字符之间的转换。 - 先计算出0~15这16个数字之间彼此异或的256种结果,然后直接转换成16进制字符,并建立起一一映射关系。建立映射的方法是,定义一个
unordered_map<int,char>trans
存储映射关系,以10进制数10
异或11
,结果为1
代表16进制数A
异或B
,结果为字符1
为例,由于ASCII共有128个字符,A
、B
的asc码分别为65、66,那么A
异或B
可以用整数 来表示。16进制数A
异或B
,结果为字符1
的映射关系在trans
中就可以用trans[65×128+66]=‘1’
表示。然后对于任意的两个16进制字符串,可以同时遍历这两个字符串,直接在trans
中就可以得到异或结果字符。
以上3个问题都解决后,代码就很容易编写了。
16进制字符串和10进制数相互转换的C++代码
#include<bits/stdc++.h>
using namespace std;
const int MAX=10005;
char temp[9],disks[1005][85000]={""};//存储所有磁盘数据
int main(){
int n,s,l,m,a,maxBlock=0;
scanf("%d%d%d",&n,&s,&l);
for(int i=0;i<l;++i){//读取l块磁盘的数据
scanf("%d%*c",&a);
fgets(disks[a],85000,stdin);
maxBlock=strlen(disks[a])/8;//maxBlock存储一块磁盘上的块数
}
scanf("%d",&m);
while(m--){
scanf("%d",&a);
int band=a/s,row=band/(n-1);//计算条带号、单磁盘上的条带号
int diskNo=(n-row%n+band%(n-1))%n,block=row*s+a%s;//计算所在磁盘号、所在磁盘上的块号
if(block>=maxBlock||(disks[diskNo][0]=='\0'&&n-l>1))//块号超过磁盘上的块数或者该磁盘被损坏且坏掉的磁盘超过1个
puts("-");//该磁盘上对应块的数据无法获取
else if(disks[diskNo][0]!='\0'){//该磁盘数据完好,直接输出对应块的数据即可
for(int i=0;i<8;++i)
putchar(disks[diskNo][block*8+i]);
puts("");
}else{//该磁盘被损坏,但数据可恢复
int ans=0,k;
for(int i=0;i<n;++i)//遍历其他的块
if(diskNo!=i){
for(int j=0;j<8;++j)//将对应的8个字符复制粘贴到temp中
temp[j]=disks[i][block*8+j];
temp[8]='\0';//temp末尾字符置\0
sscanf(temp,"%x",&k);//将16进制字符串转换为10进制数
ans^=k;//进行异或运算
}
printf("%08X\n",ans);//输出8位16进制字符串,不够8位在高位补0
}
}
return 0;
}
先计算出0~15这16个数字之间彼此异或结果的C++代码
#include<bits/stdc++.h>
using namespace std;
char itc[17]="0123456789ABCDEF";//10进制数与16进制字符的映射关系
unordered_map<int,char>trans;//存储16进制字符与异或后的结果之间的映射关系
char disks[1005][85000]={""};//存储所有磁盘数据
int main(){
for(int i=0;i<16;++i)//计算0~15这16个数字之间彼此异或的256种结果
for(int j=i;j<16;++j)
trans[itc[i]*128+itc[j]]=trans[itc[j]*128+itc[i]]=itc[i^j];
int n,s,l,m,a,maxBlock=0;
scanf("%d%d%d",&n,&s,&l);
for(int i=0;i<l;++i){//读取l块磁盘的数据
scanf("%d%*c",&a);
fgets(disks[a],85000,stdin);
maxBlock=strlen(disks[a])/8;//maxBlock存储一块磁盘上的块数
}
scanf("%d",&m);
while(m--){
scanf("%d",&a);
int band=a/s,row=band/(n-1);//计算条带号、单磁盘上的条带号
int diskNo=(n-row%n+band%(n-1))%n,block=row*s+a%s;//计算所在磁盘号、所在磁盘上的块号
if(block>=maxBlock||(disks[diskNo][0]=='\0'&&n-l>1))//块号超过磁盘上的块数或者该磁盘被损坏且坏掉的磁盘超过1个
puts("-");//该磁盘上对应块的数据无法获取
else if(disks[diskNo][0]!='\0'){//该磁盘数据完好,直接输出对应块的数据即可
for(int i=0;i<8;++i)
putchar(disks[diskNo][block*8+i]);
puts("");
}else{//该磁盘被损坏,但数据可恢复
char ans[9]="00000000";//存储对应块的数据
for(int i=0;i<n;++i)//遍历其他的块
if(diskNo!=i)
for(int j=0;j<8;++j)//求出异或结果
ans[j]=trans[ans[j]*128+disks[i][block*8+j]];
puts(ans);
}
}
return 0;
}