题目
题目来源:CCF 吉林省选 2011;
在线评测地址:Luogu#4305;
运行限制:时间 (1.00 extrm{s}),空间 (128 extrm{MiB})。
题目描述
给定 (n) 个数,要求把其中重复的去掉,只保留第一次出现的数。
输入格式
本题有多组数据。
第一行一个整数 (T),表示数据组数。
对于每组数据:
第一行一个整数 (n)。
第二行 (n) 个数,表示给定的数。
输出格式
对于每组数据,输出一行,为去重后剩下的数,两个数之间用一个空格隔开。
数据规模及约定
- 对于 (30\%) 的数据,(nle 100),给出的数 (in [0, 100])。
- 对于 (60\%) 的数据,(nle 10^4),给出的数 (in [0, 10^4])。
- 对于 (100\%) 的数据,(1le Tle 50),(1le nle 5 imes 10^4),给出的数在 (32) 位有符号整数范围内。
分析
题意很明确,问题就是怎么解决。我们要实现两个操作,即:
- 询问一个数是否在集合内;
- 将一个数插入集合中。
(60 exttt{pts})
我们可以用 set,复杂度是 (mathcal{O}(Tsum (nlog n))),带入极限数据大约是 (50 imes 5 imes 10^4 imes 16=4 imes 10^7),由于平衡树常数大会被卡常而无法通过。
(100 exttt{pts})
Solution 1
这时,我们会想到用哈希表。哈希表的均摊复杂度是 (mathcal{O}(Tsum n)),可以轻松通过这题。
但是一般的哈希表会被卡,又不想写多模数,那么怎么办呢?unordered_set 闪亮登场。
这是 C++11 新增的一个数据结构,对应着一个哈希表。有着官方加持的哈希表跑得飞快,吸氧以后就能过了。
Solution 2
当然,我们还有另一种方法——离散化。
但是,一般的排序算法的极限复杂度就是 (mathcal{O}(nlog n)),依旧会被卡(但是比平衡树好很多)。但是如果用基数排序呢?
单次排序的复杂度就是 (mathcal{O}(nlog_{10} x)),可以比较高效,实际性能待检测。
Code
用了第一种做法,在洛谷少爷机上最慢跑了 (900 extrm{ms}) 左右,很悬。正解应该还是 Hash。
#include <cstdio>
#include <unordered_set>
using namespace std;
unordered_set<int> st;
int main()
{
int cas, n, tmp;
scanf("%d", &cas);
while (cas--)
{
st.clear();
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
scanf("%d", &tmp);
if (st.find(tmp) == st.end())
{
printf("%d ", tmp);
st.insert(tmp);
}
}
putchar('
');
}
return 0;
}