洛谷 P4305 [JLOI2011] 不重复数字 题解
洛谷 P4305 [JLOI2011] 不重复数字 题解
题目描述
原题链接:[洛谷 P4305 [JLOI2011] 不重复数字 题解
给定 \(n\) 个数,要求把其中重复的去掉,只保留第一次出现的数。(\(1 \le T\le 50\),\(1 \le n \le 5 \times 10^4\),给出的数在 \(32\) 位有符号整数范围内)
题意分析
很显然是一道考察哈希表的题。
哈希 (Hash) 表
以这道题为例,如果给出的数值域较小,这道题可以使用计数排序的方法解决。但是给出的数范围太大,开不了这么大的数组,怎么办?
可以定义一个常量 mod,如果两个数对 mod 取模得到的值相同,就认为这两个数相同,这样只需要开一个大小为 mod 的数组即可。但很明显,这个算法是错误的,所以要考虑优化算法。
可以开一个 vector<int> linker[mod],将每个数字插入到下标为它对 mod 取模的位置。这样既能保证算法的正确性,又能降低代码的时间复杂度。这种数据结构就叫哈希 (Hash) 表。代码如下:
#include <iostream>
#include <cstdio>
#include <vector>
#define mod 999983
using namespace std;
inline void insert(vector<int> linker[], int x) {
int hash = x % mod;
for (int i = 0; i < linker[hash].size(); i++)
if (linker[hash][i] == x) return; // 如果找到和它相同的数,则不输出
cout << x << " ";
linker[hash].push_back(x); // 插入这个数字
}
int T, n, x;
int main() {
scanf("%d", &T);
while (T--) {
vector<int> linker[mod];
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &x);
insert(linker, x);
}
cout << endl;
}
return 0;
}
这里的 mod 会取一个比较大的素数,而且在空间足够的情况下越大越好。常用的 mod 有 \(10007\)、\(23333\)、\(999983\) 等。
但是这份代码提交上去之后,会有 4 个测试点 RE,所以我们还要改正算法。
算法改进
题目中说:给出的数在 \(32\) 位有符号整数范围内,也就是这个数有可能是负数。负数取模之后是非正数,但是数组没有负下标,所以会 RE。
负数对 mod 取模后得数会在 \((-mod,0]\) 区间内,加上 mod 之后,得数就会变成正数,此时再对 mod 取模就能保证得数在 \([0,mod)\) 区间内。只需要修改 insert 函数。
inline void insert(vector<int> linker[], int x) {
int hash = (x % mod + mod) % mod; // 防止负下标越界
for (int i = 0; i < linker[hash].size(); i++)
if (linker[hash][i] == x) return; // 如果找到和它相同的数,则不输出
cout << x << " ";
linker[hash].push_back(x); // 插入这个数字
}
使用 STL 容器
没想到吧,这道题竟可以用 STL 来解!我们要使用的 STL 容器叫做 map。(需要 #include <map>)map 关联集合本质是一棵红黑树(一种平衡二叉树),可以将它看作是下标为任意类型的数组【常用的下标类型有整型(可以是负数)和字符串类型】。下面介绍 map 的用法。
map <A, B> ds:建立一个名叫ds、下标类型为A、元素类型为B的数组。ds[A] = B:将数组中下标为A的位置的值变成B。ds[A]:访问数组中下标为A的元素。注意:如果这个下标对应的元素不存在,则会自动创建下标为A、值为默认值(所有数值类型为 \(0\)、string类型为空串)的元素。ds.end():返回数组末尾地址。ds.find(x):查询下标x在数组中的地址。若这个数不存在,则返回ds.end()。因此,这条语句经常和ds.end()搭配使用,用来判断某一个下标是否存在。ds.empty():判断数组是否为空。ds.size():判断数组元素个数。ds.erase(A):删除数组下标为A的元素。
所以,这道题就可以使用 map 轻松解决。代码实现如下:
#include <cstdio>
#include <map>
using namespace std;
int T, n, x;
int main() {
scanf("%d", &T);
while (T--) {
map <int, bool> ds; // 元素为什么类型都行
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", &x);
if (ds.find(x) != ds.end()) continue; // 如果这个数已经存在,则跳过
printf("%d ", x);
ds[x] = 1;
}
printf("\n");
}
return 0;
}

浙公网安备 33010602011771号