2019年蓝桥杯省赛-修改数组(并查集)

0.题目

时间限制: 1.0s 内存限制: 256.0MB 本题总分:20 分
【问题描述】
给定一个长度为 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.题解

1.1 暴力枚举

思路

思路非常好想,使用一个bool数组标记当前位是否被选过
两层循环(一层for,一层while),外层for用于遍历输入的数, while用于从头开始判断标记位,是则停止,标记;否则不断将当前数++;
但是总体耗时 O(n^2), 很明显会超时!

代码

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1000010;

int n;
int a[N];
bool st[N];

int main()
{
    cin >> n;
    for (int i = 0; i < n; i++) scanf("%d", &a[i]);
        
    st[a[0]] = true;
    for (int i = 1; i < n; i ++)
    {
        if (!st[a[i]]) st[a[i]] = true;
        else
        {
            while (st[a[i]]) a[i] ++;
            st[a[i]] = true;
        }
    }
    
    for (int i = 0; i < n; i++) printf("%d ", a[i]);
    return 0;
}

1.2 并查集

思路

这题的难点主要是能否想到用并查集来解决,我们想要知道我们每次选到某个数,究竟是输出其本身还是其他数?
我们模拟一下思路,如果我们选择1, 那我们首先去找1是否被选择,如果被选择了,然后再寻找2是否被选择....最后找到那个未被选择的数输出.但这样总体时间复杂度高达O(n^2),并不是我们想要的; 我们想要知道我们如果选择1, 如果重复,立即能知道我们应该选择的下一个数.
如果我们修改标记位由bool记录是否重复 -> 记录下一个位置呢?
比如像我们第一次选择了1, 那么flag[1] = 1 + 1 = 2, 再次选择就是 flag[1] = 2 + 1 = 3呢?
这里又有一个问题了,我们维护的难题, 像这里应该同时更新 flag[2] = 2 + 1 = 3; 也就是说我们要同时所有更新值为 flag[1] 的数均 + 1; 这样维护的成本又变成O(n^2)
如何既要更新记录值,又能很好的维护标记呢?

我们想到一种很有用的结构:树以及对应的结构--并查集
我们可以将每个数视为一个节点,节点的值表示该数。我们初始化时,每个数的记录值为它的下一个数,即记录值为当前数加一。
然后,我们通过 find 操作来寻找当前数的记录值。如果当前数的记录值等于它本身,说明它还没有被选择过,直接返回当前数;否则,我们递归地调用 find 操作,找到记录值对应的下一个数,直到找到一个未被选择的数为止(这里如果使用路径压缩,每次find操作会及时维护更新并查集,使树的高度始终维持在较小的范围内,可以默认寻找效率为O(1))。最后,我们将当前数的记录值更新为下一个未被选择的数,以便下一次选择数时能够快速找到。
这样,每次选择数时,我们只需通过 find 操作来找到下一个未被选择的数,时间复杂度为 O(1),非常高效

代码

#include<bits/stdc++.h>
using namespace std;
const int Maxn = 1e6 + 10;
int fa[Maxn + 1];

void init(){
	for(int i = 1; i <= Maxn; i++){
		fa[i] = i;
	}
}

// 压缩路径 
int find(int x){
	if(x == fa[x]){
		return x;
	} else{
		fa[x] = find(fa[x]);
		return fa[x];
	}
}

// 根节点合并 
void merge(int x, int y){
	fa[find(x)] = find(y); 
}

int main(){
	int n;
	cin >> n;
	init();
	for(int i = 0; i < n; i++){
		int a;
		cin >> a;
		int t = find(a); // 下一个未被选择的数
		cout << t << " ";
		merge(t, t+1); // 下一个未被选择的数(根节点)更新为 find(t+1)
	}
	return 0;
} 
posted @   DawnTraveler  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示