【备战蓝桥杯】4.修改数组——并查集的巧妙使用

212 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

题目描述

给定一个长度为 N 的数组 A = [A1, A2, · · · AN],数组中有可能有重复出现的整数。 现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改 A2, A3, · · · , AN。 当修改 Ai 时,小明会检查 Ai 是否在 A1 ~ Ai-1 中出现过。如果出现过,则小明会给 Ai 加上 1 ;如果新的 Ai 仍在之前出现过,小明会持续给 Ai 加 1 ,直到 Ai 没有在 A1 ~ Ai-1 中出现过。 当 AN 也经过上述修改之后,显然 A 数组中就没有重复的整数了。现在给定初始的 A 数组,请你计算出最终的 A 数组。

【输入格式】 第一行包含一个整数 N。
第二行包含 N 个整数 A1, A2, · · · , AN 。
【输出格式】 输出 N 个整数,依次是最终的 A1, A2, · · · , AN。

【样例输入】

5
2 1 1 3 4

【样例输出】

2 1 3 4 5

【评测用例规模与约定】
对于 80% 的评测用例,1 ≤ N ≤ 10000。
对于所有评测用例,1 ≤ N ≤ 100000,1 ≤ Ai ≤ 1000000。

思路

首先对于前百分之八十的数据,一个可行的写法就是暴力模拟,每遍历一个数组元素就加1直到有空位为止。但这样明显会超复杂度。

对于在Ai范围内的每个数字,我们需要一个更简单的耗时更短方法去寻找大于它并且挨得最近的空位,并在补全这个空位后能够寻找到下一个大于且最近的空位。

一个很巧妙的思路就是使用并查集的思想。先看看并查集的操作。有一堆集合,每个数字必定存在且只存在于一个集合中。每个集合有一个唯一的“代表”。对于每个数字它可以在平均lgn的时间内找到这个数字所在集合的“代表”。

在这道题中,我们令每个空位都是一个集合的代表。 每当一个空位i被填补时,就可以令这个ii+1集合合并。本来i是一个集合的代表,现在i所在集合的所有元素的代表被i+1所在集合的代表所接替,并且仍然是大于且最近的。这样的话这个性质就可以一直保持下去。

详情见代码。其中findf函数就是寻找“代表”的函数。

AC代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1000000+5;
int node[maxn];
int arr[maxn];
int findf(int n){
	return node[n]=node[n]==n?n:findf(node[n]);
}
int main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>arr[i];
	}	
	for(int i=0;i<maxn;i++){
		node[i]=i;
	}
	for(int i=0;i<n;i++){
		if(node[arr[i]]==arr[i]){
			node[arr[i]]=findf(arr[i]+1);
		}else{
			arr[i]=findf(arr[i]+1);
			node[arr[i]]=findf(arr[i]+1);
		}
		cout<<arr[i]<<' ';
	}
	
}

总结

这道题巧妙地利用了并查集的思想,佩服佩服。做完这道题,对于并查集的理解更加深刻了。并查集中,每个集合中的元素不一定是同一类,就像这道题,代表是空的,非代表不是空的。不过,两者一定要有一个可以转变的关系,这样才能更好的利用并查集。