CCF认证 201903-3损坏的RAID5

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/richenyunqi/article/details/89206423

欢迎访问我的CCF认证考试题解目录哦 https://blog.csdn.net/richenyunqi/article/details/83385502

题目描述

CCF认证 201903-3损坏的RAID5描述

算法设计

这道题是我在csp上见过的第3题中测试数据量最大的一道题。为了能AC这道题我们需要解决3个问题:

  1. 读取数据
    由于磁盘数量最大为 1 0 3 10^3 ,每块磁盘最大容量为 40 K B 40KB ,一个字节需要用2个16进制字符来表示,所以一块磁盘最大需要约 8 × 1 0 4 8×10^4 个字符来表示,故输入的所有磁盘数据最多有 0.8 × 1 0 8 0.8×10^8 个字符,这个数字达到了 1 0 8 1 10^8≈1 亿个字符级别。我编写好算法后,使用了各种优化方法都只能拿到70分,最后3个测试点总是过不去,后来才发现是数据量太大,读取数据的时候就已经超时了。为此我尝试了C语言中scanf、gets、fgets,C++中cin、getline这5个函数去读取字符串,并编写了博客C语言中scanf、gets、fgets,C++中cin、getline读取字符串的效率比较。最终使用了读取效率最高的fgets函数,才最终得以AC。所以我也建议读者使用fgets函数去读取输入数据。
  2. 根据输入的块号,确定该块位于那个磁盘的第几块
    以下涉及到的所有编号都从0开始
    首先说一下磁盘上条带号是如何排布的,如下图,图中编号为条带号:
    在这里插入图片描述
    有了输入的块号,以下直接给出计算该块所在磁盘号和所在磁盘上的块号的公式(%代表取模运算):
    = / 条带号=\lfloor 输入块号/一个条带有几块 \rfloor
    = / ( 1 ) 单磁盘上的条带号=\lfloor 条带号/(磁盘总数-1) \rfloor
    = ( % + % ( 1 ) ) % 所在磁盘号=(磁盘综述-单磁盘上的条带号\%磁盘总数+条带号\%(磁盘总数-1))\%磁盘总数
    = × + % 所在磁盘上的块号=单磁盘上的条带号×一个条带有几块+输入块号\%一个条带有几块
  3. 如何对两个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个字符,AB的asc码分别为65、66,那么A异或B可以用整数 65 × 128 + 66 65×128+66 来表示。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;
}

猜你喜欢

转载自blog.csdn.net/richenyunqi/article/details/89206423
今日推荐