L3-1 Hashing - Hard Version (30 分)
Given a hash table of size N, we can define a hash function H(x)=x%N. Suppose that the linear probing is used to solve collisions, we can easily obtain the status of the hash table with a given sequence of input numbers.
However, now you are asked to solve the reversed problem: reconstruct the input sequence from the given status of the hash table. Whenever there are multiple choices, the smallest number is always taken.
Each input file contains one test case. For each test case, the first line contains a positive integer N (≤1000), which is the size of the hash table. The next line contains N integers, separated by a space. A negative integer represents an empty cell in the hash table. It is guaranteed that all the non-negative integers are distinct in the table.
For each test case, print a line that contains the input sequence, with the numbers separated by a space. Notice that there must be no extra space at the end of each line.
11
33 1 13 12 34 38 27 22 32 -1 21
1 13 12 21 33 34 38 27 22 32
从给定的哈希表反推出可以生成该哈希表的输入序列,若存在多种输入序列同时满足给定的哈希表,输出其中最小的那个序列。
检查给定的哈希表,将每个元素对表长的余数标记如下:
元素 | 33 | 1 | 13 | 12 | 34 | 38 | 27 | 22 | 32 | -1 | 21 |
---|---|---|---|---|---|---|---|---|---|---|---|
下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
余数 | 0 | 1 | 2 | 1 | 1 | 5 | 5 | 0 | 10 | / | 0 |
与命中元素的距离 | 0 | 0 | 0 | 2 | 3 | 0 | 1 | 7 | 9 | / | 0 |
观察上表,容易发现每个命中哈希表的元素(记该元素为 e h e_h eh)必定是与同余元素相比,在原序列中第一个出现的,而对于未命中元素(记该元素为 e l e_l el ),由线性探测法的原理可知, l l l 被放置在哈希表中的某个单元时一定满足条件: e l e_l el 和 e h e_h eh 之间无空位。
根据上述讨论,我们已经可以从模拟角度推算出样例哈希表对应的原序列了。
由于题目要求输出所有可能序列中最小的一个,应该尽可能将较小的元素优先输出,故首先对所有元素进行升序排序,得到顺序列表 l i s t list list。接着,对每个元素进行计算,设当前元素为 e e e:
前文讨论出的规律,可以进一步转化为图的规律:每个元素抽象为图的一个顶点,有向边则是由命中元素 e h e_h eh 指向与其具有相同余数的非命中元素 e l e_l el 。于是表格中“与命中元素的距离”可以抽象为顶点的入度。每当输出一个顶点,就将其对应后继顶点的入度减一,当后继顶点(即某个 e h e_h eh 对应的一个 e l e_l el )的入度减至 0 0 0 ,我们就可以输出这个顶点。这不就相当于达到了“模拟思路”中步骤2循环所达成的效果吗?
由于同一时刻入度为0的顶点可能有多个,题目要求“尽可能优先输出较小的元素”,故我们可以使用一个升序的优先队列来保存入度为零的顶点。
令 dist ( e ) \text{dist}(e) dist(e) 表示某一元素 e e e 与同余命中元素的距离(若 e e e 本身就命中,则该距离为 0 0 0 ), pos ( e ) \text{pos}(e) pos(e) 表示 e e e 在哈希表中的下标, val ( e ) \text{val}(e) val(e) 表示元素 e e e 的值, n n n 表示哈希表的容量,可得下列公式:
dist ( e ) = ( pos ( e ) − val ( e ) % n + n ) % n \text{dist}(e) = (\text{pos}(e) -\text{val}(e) \% n + n) \% n dist(e)=(pos(e)−val(e)%n+n)%n
通过下列步骤可以计算出答案:
可见,问题转化为拓扑排序后更加清晰明了了。
#include "bits/stdc++.h"
using namespace std;
const int maxn = 1009;
priority_queue, greater > q;
vector vec;
int inOrder[maxn];
int arr[maxn];
int dist(int currPos, int val, int n){
int pos = val % n;
int d = (currPos - pos + n) % n;
return d;
}
int main(){
#ifdef TEST
freopen("test.txt", "r", stdin);
#endif // TEST
int n;
while(cin >> n){
memset(inOrder, 0, sizeof(inOrder));
for(int i = 0; i < n; i++){
int t;
cin >> t;
arr[i] = t;
vec.push_back(t);
if(t >= 0){
int d = dist(i, t, n);
inOrder[i] = d;
if(inOrder[i] == 0)
q.push(t);
}
}
bool firstOut = true;
while(!q.empty()){
int tmp = q.top();
q.pop();
if(firstOut){
cout << tmp;
firstOut = false;
}
else{
cout << " " << tmp;
}
int tmpPos = find(vec.begin(), vec.end(), tmp) - vec.begin();
for(int i = tmpPos; i < tmpPos + n; i++){
int nextNode = vec[i % n];
if(i % n == 8)
cout << "";
if(dist(i % n, nextNode, n) >= (i - tmpPos + n) % n){
if(inOrder[i % n] > -1)
inOrder[i % n]--;
if(inOrder[i % n] == 0)
q.push(nextNode);
}
}
}
}
return 0;
}
#include "bits/stdc++.h"
using namespace std;
const int maxn = 1e3+9;
int n;
bool vis[maxn];
int arr[maxn];
map positionMap;
vector vec;
bool firstOut = true;
void print(vector::iterator it){
if(firstOut){
cout << *it;
firstOut = false;
}
else{
cout << " " << *it;
}
vis[positionMap[*it]] = 1;
vec.erase(it);
}
bool okToPop(vector::iterator it){
int pos = positionMap[*it];
int prev = pos - 1;
prev = prev >= 0 ? prev : n - 1;
return vis[prev] || (*it % n == pos);
}
int dist(int v){
int pos = positionMap[v];
int cnt = 0;
int k = n;
while(k--){
if(!vis[pos])
cnt++;
if(pos == v%n)
break;
pos -= 1;
if(pos < 0) pos += n;
}
return cnt - 1;
}
void debug(){
cout << endl;
cout << "debug: ";
for(auto i : vec)
cout << i << " ";
cout << endl;
}
int main(){
#ifdef TEST
freopen("test.txt", "r", stdin);
#endif // TEST
memset(vis, 0, sizeof(vis));
cin >> n;
for(int i = 0; i < n; i++){
int k;
cin >> k;
if(k >= 0){
positionMap[k] = i;
vec.push_back(k);
}
else{
vis[i] = 1;
}
}
int len = vec.size();
sort(vec.begin(), vec.end());
int s;
while(s = vec.size()){
auto it = vec.begin();
int value = *it;
if(*it % n == positionMap[*it]){ // hash hit
print(it);
}
else{ // hash fail
int pos = positionMap[*it];
int cnt = dist(*it);
while(cnt){
for(auto i = it+1; i != vec.end(); i++){
// debug();
if(okToPop(i)){
print(i);
cnt--;
break;
}
}
}
print(it);
}
}
return 0;
}
模拟和拓扑排序相较之下,还是抽象程度更高的拓扑排序更易实现,且逻辑更清晰明白。我做此题早已推导出哈希表和潜在原序列的关联规律,但模拟实现却花了两小时有余。当然一方面是长时间未练习,编程手法有所生疏,另一方面还是思路上不够开阔,没能更进一步,抽象成拓扑排序。今后还需多加努力!