Vanis and Weird Message
题号:NC200050
时间限制:C/C++/Rust/Pascal 1秒,其他语言2秒
空间限制:C/C++/Rust/Pascal 256 M,其他语言512 M
Special Judge, 64bit IO Format: %lld

题目描述

某一天,Vanis收到了一串奇怪的序列,经过研究,他似乎知道了如何还原这段序列原本的信息。

1. 这串序列中的每个元素都是一个介于[-128, 127]的整数,即是一串原本存储于内存中连续字节单元的字节序列。
2. 序列的长度为n。
3. 序列可以被分组,每一组的开头会有一个整数h,其值只会取4或8两种值,表示在这个整数之后(不包含整数h)连续的h个数应当被视作一个整体。更确切的说,如果h是4,则之后的连续4个字节是以大端法存储的4字节int类型数据(此处指C/C++中的类型);如果h是8,则之后连续的8个字节是以大端法存储的8字节long long类型数据(此处指C/C++中的类型)。

于是Vanis想要还原这个序列,但是很不幸的是所使用的计算机在存储数据时是按照小端法存储的,于是想请你帮忙(虽然你当前所使用的计算机也是按照小端法存储数据的)。

输入描述:

第一行输入一个正整数n。
第二行输入n个用空格符分隔的整数,表示这个长度为n的序列,第i个数记作a_i

数据规范:
* .
* .
* 输入数据保证符合题目关于分组的描述。

输出描述:

每成功解析一组数,便在一行输出这个数。详见样例。
示例1

输入

复制
14
4 1 50 -112 29 8 0 7 35 65 -18 -99 13 29

输出

复制
20090909
2009090920090909
示例2

输入

复制
5
4 -1 -1 -1 -1

输出

复制
-1

备注:

如果你不了解大端存储和小端存储的含义,下面的描述可能对你解决这道问题有所帮助。(注:为了便于解释,部分描述可能未按照其严格的定义来描述)

对于CPU来说,内部存储设备逻辑上是一个线性的整体,每一字节的内存空间被称为一个内存单元,于是对所有内存单元进行逻辑上的编址(可以理解为赋予每个内存单元唯一的一个序号),之后可以通过逻辑地址寻找实际的物理地址。
对于一段连续的逻辑地址区间(如:20000、20001、20002这样),地址显然有大有小,对于序号较小者,称呼其为低地址;对于序号较大者,称呼其为高地址。
计算机中的所有数据以二进制存储,以C/C++中大小为4个字节的int类型为例,如对于整数20090909,其对应二进制则是(注:人为书写时,通常习惯上从最低位开始数起的每八位留一个空格,因为八位二进制数恰好占一个字节空间),其较高的位数称呼为高位,其较低的位置称呼为低位,如下图所示:

按照上述写法,即左侧被称为高位,右侧被称为低位,最左边的8个二进制位称为高八位,最右边的8个二进制位称为低八位。

大端法(或:大端存储、大端表示法),指的是按照高位存低地址,低位存高地址的模式存放数据的方法。
小端法(或:小端存储、小端表示法),指的是按照高位存高地址,低位存低地址的模式存放数据的方法。
如下图所示,假设这个整数20090909存放在这四个内存地址单元上,大端法与小端法的存储方式如下图:


显然二者的存储顺序刚好相反,对于这道题,题目描述中的每一分组,相当于按照大端法将一段连续的内存地址单元上的每个字节的数据输入,如果想转换成小端法,只需要将这一段数据顺序反转,即可转化为小端法的存储模式。

到目前为止,Intel兼容机大多采用小端法进行存储,所以你现在使用的笔记本、以及当前评测程序所用的服务器都是按照小端法存储数据的。如果在写程序时,使用一个指针指向某个数据的最低地址(如上图中的内存单元20000就是这个int数据的最低地址),之后进行取值,计算机就会自动按照其所使用的大小端表示读取从这个地址开始向后的一段连续地址上的数据,并将它们作为一个整体(对于上图中的小端法存储而言,在使用小端法存储的计算机中从地址20000开始读取四个字节的数据作为一个int类型值的整体,会连续读取地址为这四个内存单元,并且将20000处的8位二进制数作为这个读取出的int类型值最低的8位,因此能够正确还原20090909这个整数)。

我们先将上图每个8位转换成单字节有符号整数,如下图所示(至于10010000为什么被转换成一个负数,即-112,这与计算机有符号数的补码表示有关,不过因为其与当前问题无关,故而不在此作解释):

之后将这几个数分别按照小端和大端的顺序填入char数组(因为C语言数组是在内存中连续存放的,就如同上述的20000, 20001这样的连续地址内存单元),但是都按照小端方式读取,你可以通过运行如下两个C程序来观察现象:

程序一,小端法存储并按照小端法读取:
#include <stdio.h>
char dat[] = {29, -112, 50, 1};
int main() {
    int *p = (int *)dat;
    printf("%d\n", *p);
    return 0;
}
输出:
20090909
正确读取。

程序二,大端法存储并按照小端法读取:
#include <stdio.h>
char dat[] = {1, 50, -112, 29};
int main() {
    int *p = (int *)dat;
    printf("%d\n", *p);
    return 0;
}
输出:
495989249
错误读取。

上述程序通过char数组实现连续内存存储,之后通过指针强制类型转换,将char类型指针dat转化为int类型指针,之后进行读取输出。(备注:数组名称同时也可以当做指向其首元素地址的指针,即此处的dat相当于dat[0]的地址&dat[0],dat + 1相当于dat[1]的地址&dat[1])

上述程序说明:
1. 使用指针读取时,计算机会自动按照其所使用的大小端表示法进行读取。
2. 只有使用大端法读取大端表示存储的数据,或使用小端法读取小段表示存储的数据,才能够获得正确的结果。

对于这道题,每一分组的数据首先会有一个整数4或8代表这个整数是由几个字节组成的,如果你使用C/C++,这将决定你应当使用int指针进行读取,还是使用long long int指针进行读取。

这道题给出的整数按照大端法存储,如果你想要正确读取,你应当将其先转换为小端法存储,之后使用指针读取。(备注:这不是唯一解法)

你可以运行下述C语言程序获得样例一的输出。

#include <stdio.h>
char dat[] = {4, 1, 50, -112, 29,
            8, 0, 7, 35, 65, -18, -99, 13, 29};
void sw(char *a, char *b) {
    char t = *a;
    *a = *b;
    *b = t;
}
int main() {
    int n = 14;
    char *pos = dat;
    while (pos - dat < n) {
        if (*pos == 4) {
            for (char *s = pos + 1, *t = pos + 4; s < t; ++s, --t)
                sw(s, t);
            printf("%d\n", *((int *)(++pos)));
            pos += 4;
        } else if (*pos == 8) {
            for (char *s = pos + 1, *t = pos + 8; s < t; ++s, --t)
                sw(s, t);
            printf("%lld\n", *((long long int *)(++pos)));
            pos += 8;
        }
    }
    return 0;
}