CCF 201903-3 损坏的RAID5

分析:

本题属于大模拟中代码量较小的题目,但是难在读懂题意,可能要花很久才能明白题目想要表达的意思。

首先,分析下题意。

首先解释下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就好。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值