Harry 在 Hlink 公司工作,该公司正在为 Hoogle 开发路由硬件。最近,他被要求为 HH-932 路由芯片编写哈希软件。
让我们描述一下 HH-932 的架构。该芯片拥有 4 兆字节($4 \times 2^{20}$ 字节)的只读闪存,可以包含芯片将要执行的程序和数据。该芯片的处理器有 256 个寄存器,命名为 r0 到 r255,每个寄存器包含一个 32 位整数。在程序执行前,除 r0 外的每个寄存器都包含 0,而 r0 包含程序的输入。
该芯片还有一个特殊的寄存器,称为指令指针(instruction pointer)。程序存储在闪存中,初始时指令指针等于 0。程序的执行过程如下:解析并执行指令指针所指向地址处的指令。如果指令没有导致跳转,指令指针将增加所解析指令的大小,从而指向刚执行指令之后的内存字节。
Harry 的任务是为给定的 $n$ 个整数集合 $A = \{a_1, a_2, \dots, a_n\}$ 创建一个非常快速的哈希函数,映射到 $0$ 到 $2n - 1$ 之间的整数集合。该函数的计算必须在最多 30 条指令内终止。哈希函数不能有冲突。形式上,需要编写一个程序,满足以下条件:
- 如果程序使用集合 $A$ 中的一个整数执行,它必须在执行最多 30 条指令后终止,并返回 $0$ 到 $2n - 1$ 之间的值。
- 对于 $A$ 中的任意两个不同值 $a_i$ 和 $a_j$,返回的值必须不同。
Harry 不擅长底层编程,所以他向你求助。他发给你一张 HH-932 芯片的可能指令及其十六进制操作码的表格。他没有提供注释,你在互联网上搜索了文档,没有找到,但发现了以下补充说明:
- 所有寄存器都包含 32 位整数。
- 加法、减法和位运算不区分整数是有符号还是无符号。
- 条件跳转将寄存器解释为有符号整数。
- 乘法或除法的结果是一个有符号的 64 位整数,保存到两个寄存器中。获得结果低位的寄存器将其存储为无符号整数。
- 除法和取模运算使用 64 位整数作为被除数,低位被解释为无符号,高位被解释为有符号。除数是有符号的。
- 有符号整数的除法和取模运算方式与 x86 架构相同。
- 芯片内存中的所有整数都以低字节在前(小端序)的方式保存。
你必须创建芯片闪存的内容,以便在它作为程序执行后,计算并返回所需的哈希函数。
对于集合 $A$ 中的任何输入,你的程序不得访问 4M 范围之外的内存,且不得除以零。指令指针也不得超出 4M 范围。
指令表
| 指令 | 大小 | 操作码 | 指令功能 |
|---|---|---|---|
nop |
1 字节 | 00 |
无操作 |
add |
4 字节 | 01 r1 r2 r3 |
r3 := r1 + r2 |
sub |
4 字节 | 02 r1 r2 r3 |
r3 := r1 - r2 |
mul |
5 字节 | 03 r1 r2 r3 r4 |
(r3, r4) := r1 * r2,r3 存低位,r4 存高位 |
div |
6 字节 | 04 r1 r2 r3 r4 r5 |
(r3, r4) := (r1, r5) div r2,r1 存被除数低位,r5 存被除数高位,r3 存商低位,r4 存商高位 |
mod |
5 字节 | 05 r1 r2 r3 r4 |
r3 := (r1, r4) mod r2,r1 存被除数低位,r4 存被除数高位 |
and |
4 字节 | 10 r1 r2 r3 |
r3 := r1 and r2 |
or |
4 字节 | 11 r1 r2 r3 |
r3 := r1 or r2 |
xor |
4 字节 | 12 r1 r2 r3 |
r3 := r1 xor r2 |
neg |
2 字节 | 20 r1 |
r1 := -r1 |
not |
2 字节 | 21 r1 |
r1 := ~r1 |
load |
3 字节 | 30 r1 r2 |
r1 := memory[r2],从地址 r2 开始的 4 字节复制到 r1,低字节在前 |
put |
6 字节 | 31 r1 b0 b1 b2 b3 |
r1 := (b0, b1, b2, b3),b0 为低字节,b3 为高字节 |
jmp |
5 字节 | 40 b0 b1 b2 b3 |
跳转到地址 (b0, b1, b2, b3) |
jz |
6 字节 | 41 r1 b0 b1 b2 b3 |
若 r1 == 0 则跳转到地址 (b0, b1, b2, b3) |
jnz |
6 字节 | 42 r1 b0 b1 b2 b3 |
若 r1 != 0 则跳转到地址 (b0, b1, b2, b3) |
jg |
6 字节 | 43 r1 b0 b1 b2 b3 |
若 r1 > 0 则跳转到地址 (b0, b1, b2, b3) |
jge |
6 字节 | 44 r1 b0 b1 b2 b3 |
若 r1 >= 0 则跳转到地址 (b0, b1, b2, b3) |
jl |
6 字节 | 45 r1 b0 b1 b2 b3 |
若 r1 < 0 则跳转到地址 (b0, b1, b2, b3) |
jle |
6 字节 | 46 r1 b0 b1 b2 b3 |
若 r1 <= 0 则跳转到地址 (b0, b1, b2, b3) |
ret |
1 字节 | ff |
程序返回,返回值为 r0 |
输入格式
输入文件的第一行包含 $n$ ($1 \le n \le 100\,000$)。第二行包含 $n$ 个不同的整数 $a_1, a_2, \dots, a_n$ ($1 \le a_i \le 10^9$)。
输出格式
输出芯片闪存的内容。你可以输出 1 到 4\,194\,304 之间的任意字节数。内存的其余部分填充为零。每个字节必须打印为两位十六进制数字。不要打印空格。
请仅输出程序所需的内存前缀,不要输出末尾的零,以加快测试速度。
样例
输入 1
3 2 3 9
输出 1
3101040000000500010003ff
输入 2
2 6 10
输出 2
300000ff00000000000001000000
说明
在第一个样例中,程序的解析和执行过程如下:
| 操作码 | 指令 | 执行操作 | 说明 |
|---|---|---|---|
31 01 04 00 00 00 |
put r1 4 |
r1 := 4 |
r0 = a_i, r1 = 4 |
05 00 01 00 03 |
mod (r0, r3) r1 r0 |
r0 := (r0, r3) mod r1 |
r0 = a_i mod 4, r1 = 4 |
ff |
ret |
return r0 |
返回 a_i mod 4 |
在第二个样例中,程序的解析和执行过程如下:
| 操作码 | 指令 | 执行操作 | 说明 |
|---|---|---|---|
30 00 00 |
load r0 r0 |
r0 := memory[r0] |
若 a_i = 6 则 r0 = 0,若 a_i = 10 则 r0 = 1 |
ff |
ret |
return r0 |
返回 0 或 1 |
注意,除了程序代码外,第二个样例中的内存还包含一些用作返回值的额外数据。