分析:
本题属于大模拟中代码量较小的题目,但是难在读懂题意,可能要花很久才能明白题目想要表达的意思。
首先,分析下题意。
首先解释下RAID5的基本原理,就是异或运算的特殊性质。比如1 ^ 0 = 1,这三个数中的任意两个数异或均可得到另外一个数,推广至n个数也是一样的。所以,当有硬盘损坏时,可由其它盘对应位置的数字异或得到损坏硬盘上的数据。
这段话是理解题意的敲门砖,理解了这段话差不多就理解了一半题意。正如上面所说,我们将每块硬盘(Disk)都分成若干个小块,总共n + 1个硬盘,其中的一个是校验盘P,如上图所示,一共四个盘,第一轮:选最后一个盘作为P,然后选下一个盘(disk0)作为块编号的起点,编号自左向右递增;第二轮:选倒数第二个盘作为P,然后选下一个盘(disk3)作为编号的起点紧接着第一轮的编号。可见弄清编号的规则首先需要弄清每一轮序号递增时P在哪个盘,当盘的编号为0 到 n时,P的位置依次为n,n-1,...,0,n,n-1,...。至于上面说到的条带的存储顺序,看样例二方可理解。
观察上图,首先可以看见之前的结论还是成立的,P的位置是从最后一个盘依次向左的。不同的是,这里的条带大小是2个块,条带意味着分组,P的位置选定后,序号递增首先填充满P后一个硬盘的条带,再继续填充下一个硬盘的条带,这就是序号填充的规则。
那么题意是什么呢?就是说现在有一组RAID5,每块大小是4个字节,首先读入三个整数n-总的硬盘数目,s-条带大小,l-现存硬盘的数目,然后读入现存硬盘的数据(16进制读入,每两个字符表示一个字节,八个字符就是一个数据块)。接着就读入m组序号,让你输出序号对于块的数据。如果该块在现存的硬盘中,则直接输出,否则尝试下异或能不能得到损坏的块,不能就输出减号。
题目想要表达的意思就是让我们根据数据块编号输出数据,理解了题意就相当于做对了一半题目。
下面正式求解本题:
最重要的问题,如何根据数据块的编号找到对应的数据?需要我们找到该编号处在哪个盘的哪个块中。
int m = s * (n - 1);//一轮有几个编号
int x = num / m;//第几轮
int p = n - (x % n + 1);//第几块硬盘为P
int pos = ((num % m + s) / s + p) % n;//处在第几块硬盘
int st = x * s + num % s;//硬盘上的块号
设num为编号,将序号的一次递增(一次填满所有的硬盘)作为一轮,换而言之,有几个P就有几轮, 条带大小s表示条带有几个块,也就是有几个序号,每轮序号递增时会有一块硬盘上是P,所以对于n块磁盘来说,每一轮占的序号数为m = s * (n - 1),序号num处在x = num / m轮上,比如上图每一轮序号数m = 4,编号为4时,4 / 4 = 1,处在第一轮上(轮数从0开始)。根据上面的分析,我们知道要想找到编号的位置需要先找到P的位置,第0轮P在disk n - 1上,第1轮P 在disk n - 2上,第n - 1轮在disk 0上,故第x轮的P在n - (x % n + 1)的disk上,我们找到了P的磁盘号,又知道了一轮一共有几个编号,每个磁盘条带会消耗几个序号,自然很容易得到num在第几个磁盘上,pos = ((num % m + s) / s + p) % n。num % m表示num是该轮的第几个编号,除以s表示是P后面的第几个磁盘。最后只用求num在disk pos上的具体下标了,我们知道前x - 1轮一共消耗了x * s个下标,再加上num % s就可以得到在条带上的具体位置了。
不要忘了还需要封装求一个16进制数异或的函数,具体见代码,注意只需要每次异或出需要求解编号对应块的数据即可,不需要将缺失的全部异或出来。
七十分代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int n, s, l, m, siz,sum;
vector<string> v(1005);
char str[82000];
int transfer1(char c) {//16进制转10进制
if (c >= '0' && c <= '9') return c - '0';
else {
return c - 'A' + 10;
}
}
char transfer2(int n) {//10进制转16进制
if (n < 10) return n + '0';
else {
return n - 10 + 'A';
}
}
void yihuo(vector<string> &v, int pos,int st) {
for (int i = st; i < st + 8; i++) {
if (v[pos][i] != '0') continue;//已经异或过了的块
int t = 0;
for (int j = 0; j < n; j++) {
t = t ^ transfer1(v[j][i]);
}
v[pos][i] = transfer2(t);
}
}
//找到每轮P所在硬盘位置,再正着排
string solve(int num) {//返回待查找块的结果
int m = s * (n - 1);//一轮有几个编号
int x = num / m;//第几轮
int p = n - (x % n + 1);//第几块硬盘为P
int pos = ((num % m + s) / s + p) % n;//处在第几块硬盘
int st = x * s + num % s;//硬盘上的块号
if (!v[pos].size() || st * 8 >= siz) return "-";
if (n - l == 1 && sum == pos) {
yihuo(v, sum, st * 8);
}
return v[pos].substr(st * 8,8) ;
}
int main() {
int num;
scanf("%d%d%d", &n, &s, &l);//n-硬盘数目,s-条带大小,l-现存硬盘的数目
sum = (n - 1) * n / 2;
for(int i = 0; i < l; i++) {
scanf("%d%s", &num,str);
v[num] = str;
sum -= num;
}
for (int i = 0; i < l; i++) {
if (v[i].size()) {
siz = v[i].size();
break;
}
}
if (n - l == 1) {//有一块损坏的硬盘
v[sum] = string(siz, '0');
}
scanf("%d", &m);
while (m--) {
scanf("%d", &num);
printf("%s\n",solve(num).c_str());
}
return 0;
}
上面七十分代码超时了,将scanf、printf换成cin,cout再禁用同步就ac了。
100分代码:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int n, s, l, m, siz,sum;
vector<string> v(1005);
string str;
int transfer1(char c) {
if (c >= '0' && c <= '9') return c - '0';
else {
return c - 'A' + 10;
}
}
char transfer2(int n) {
if (n < 10) return n + '0';
else {
return n - 10 + 'A';
}
}
void yihuo(vector<string> &v, int pos,int st) {
for (int i = st; i < st + 8; i++) {
if (v[pos][i] != '0') continue;
int t = 0;
for (int j = 0; j < n; j++) {
t = t ^ transfer1(v[j][i]);
}
v[pos][i] = transfer2(t);
}
}
//找到每轮P所在硬盘位置,再正着排
string solve(int num) {//返回待查找块的结果
int m = s * (n - 1);//一轮有几个编号
int x = num / m;//第几轮
int p = n - (x % n + 1);//第几块硬盘为P
int pos = ((num % m + s) / s + p) % n;//处在第几块硬盘
int st = x * s + num % s;//硬盘上的块号
if (!v[pos].size() || st * 8 >= siz) return "-";
if (n - l == 1 && sum == pos) {
yihuo(v, sum, st * 8);
}
return v[pos].substr(st * 8,8) ;
}
int main() {
ios_base::sync_with_stdio(false);
int num;
cin>>n>>s>>l;//n-硬盘数目,s-条带大小,l-现存硬盘的数目
sum = (n - 1) * n / 2;
for(int i = 0; i < l; i++) {
cin>>num>>str;
v[num] = str;
sum -= num;
}
for (int i = 0; i < l; i++) {
if (v[i].size()) {
siz = v[i].size();
break;
}
}
if (n - l == 1) {//有一块损坏的硬盘
v[sum] = string(siz, '0');
}
cin>>m;
while (m--) {
cin>>num;
cout<<solve(num)<<endl;
}
return 0;
}
总结:CCF给的样例都是极为简单的,通过样例提交零分的还是很常见的。对本题而言,提交零分,说明编号的公式推导错误;提交运行错误,说明没有考虑到输入的是超出范围的编号,会导致数组越界;提交超时,真是个较为奇怪的错误,公认的cin,cout比scanf,printf慢很多倍,即使禁用同步了依旧比不上scanf的速度,但是本题考虑是读入char*需要转化为string较为耗时,所以以后用scanf读入还是不要再转string了,需要直接读string用cin就好。