AC:普通数组
vis 数组统计每个数被访问的次数,如果一个数被访问了 k 次,则该数的后 k 个数一定都存在,则直接跳转到 k 后面继续判断。
还有需要注意的是:在输入输出数据很大的时候,建议使用 scanf 和 printf 取代 cin 和 cout,不然可能会超时。这道题就是。
#include <iostream>
using namespace std;
const int MaxNum = 1000005;
int vis[MaxNum];
int main() {
int n;
cin >> n;
while (n--) {
int num;
scanf("%d", &num);
while (vis[num]) {
int tmp = num;
num += vis[num];
++vis[tmp];
}
vis[num] = 1;
printf("%d ", num);
}
return 0;
}
map:超时
对于同样的思路,如果使用 map 则会超时,因为使用 [] 或 find 成员函数访问时都是要遍历查找的,没有普通数组那么快。
#include <iostream>
#include <map>
using namespace std;
int main() {
int n;
cin >> n;
map<int, int> nums;
while (n--) {
int num;
cin >> num;
map<int, int>::iterator iter = nums.find(num);
while (iter != nums.end()) {
int tmp = num;
num += iter->second;
++nums[tmp];
iter = nums.find(num);
}
nums[num] = 1;
printf("%d ", num);
}
return 0;
}
AC:并查集
思路:对于所有的数,起初他们的根节点都指向自己,表示没出现过。当遇到一个数时,找到他的根节点,由于根节点始终是未出现过的,所以输出根节点,接着把根节点再指向一个新的根节点(比旧的根节点大 1),这样就能始终保持根节点是未出现过的了。
在查找根节点的同时使用路径压缩,可以更快的查找到根节点。
6
1 2 3 4 1 3
遇到 1,1 的根节点是 1,输出 1,指向新的根节点 2,也就是 par[1] = 1 + 1;
遇到 2,2 的根节点是 2,输出 2,指向新的根节点 3
遇到 3,3 的根节点是 3,输出 3,指向新的根节点 4
遇到 4,4 的根节点是 4,输出 4,指向新的根节点 5
遇到 1,1 的根节点是 5,输出 5,指向新的根节点 6
遇到 3,3 的根节点是 6,输出 6,指向新的根节点 7
#include <iostream>
using namespace std;
const int MaxNum = 1000005;
int par[MaxNum];
void init() {
for (int i = 0; i < MaxNum; ++i) par[i] = i;
}
int find(int x) {
if (par[x] == x)
return x;
else
return par[x] = find(par[x]);
}
int main() {
int n;
cin >> n;
init();
while (n--) {
int num;
scanf("%d", &num);
int root = find(num);
printf("%d ", root);
par[root] = root + 1;
}
return 0;
}
比较
普通数组:228ms
并查集:39ms