[蓝桥杯 2019 省 A] 修改数组题解

题目跳转链接:[蓝桥杯 2019 省 A] 修改数组 - 洛谷

虚假的题解: 

题目内容就不复述了,大家可以点击上面的连接去查看;主要想分享一下这道题的解法确实很巧妙;做的时候一看题目描述很简单,上来两三下就写完,结果很意料之中啊,果然只得了80分:

附上80分代码:

#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define ll long long
int main()
{
    int n;
    cin >> n;
    vector<int> num(n);
    for (int i = 0; i < n; ++i)
    {
        cin >> num[i];
    }
    unordered_map<int, int> mp;
    for (int i = 0; i < n; ++i)
    {
        while (mp.find(num[i]) != mp.end())
        {
            num[i]++;
        }
        mp[num[i]]++;
    }
    for (int i = 0; i < n; ++i)
    {
        cout << num[i] << " ";
    }
    return 0;
}

做之前确实想到肯定要卡时间复杂度的,所以偷偷看了一眼提示写到了“并查集”,但是感觉并查集的时间复杂度也会超时,不过根据提示还是让GPT帮忙生成了一个并查集的模板,套了一下发现依然80,也是意料之中:

附上并查集80分模板:

#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define ll long long
class UnionFind
{
private:
    vector<int> parent; // 存储每个元素的父节点
    vector<int> rank;   // 存储树的深度,用于优化

public:
    // 构造函数,初始化并查集
    UnionFind(int size) : parent(size), rank(size, 0)
    {
        for (int i = 0; i < size; ++i)
        {
            parent[i] = i; // 初始时,每个元素的父节点是它自己
        }
    }

    // 查找操作,带路径压缩
    int find(int x)
    {
        if (x != parent[x])
        {
            parent[x] = find(parent[x]); // 路径压缩
        }
        return parent[x];
    }

    // 合并操作
    bool unionSets(int x, int y)
    {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY)
        {
            return false; // 已经是同一棵树
        }
        // 根据树的深度来合并,使树保持较平衡
        if (rank[rootX] > rank[rootY])
        {
            parent[rootY] = rootX;
        }
        else if (rank[rootX] < rank[rootY])
        {
            parent[rootX] = rootY;
        }
        else
        {
            parent[rootY] = rootX;
            rank[rootX]++; // 如果两棵树的深度相同,就将其中一个的深度加1
        }
        return true;
    }
};

int main()
{
    int n;
    cin >> n;
    vector<int> num(n);
    UnionFind u(N);
    cin >> num[0];
    for (int i = 1; i < n; ++i)
    {
        cin >> num[i];
        while (u.find(num[i]) == u.find(num[i - 1]))
        {
            num[i]++;
        }
        u.unionSets(num[i - 1], num[i]);
    }
    for (int temp : num)
    {
        cout << temp << " ";
    }
    return 0;
}

最后楼主直接前去看题解,直呼“妙啊妙啊”,最后说一下看了题解之后楼主捋清的思路:

真正题解:

首先初始化都差不多,就是所有元素最开始都是指向自己的,也就是自己的父亲序号就是自己的值,f[i]==i;,然后当我们输入了一个元素之后,我们将这个元素的父亲序号加1,每次只输出输入节点的父亲序号。乍一看很绕口,这句话怎么进一步理解呢,假如我们第一个输入的元素的4, 那么正如上面所说的,我们输出4的父亲序号(因为是第一个元素,所以4的父亲序号就是自己,也就是4),那么进行输出操作后,输出完没结束呢,我们要将4的父亲序号从4变成5,为什么要这么加1呢,因为如果后面我们遇到的输入还是4的话,我们要做的是不是也是跟上面一样输出4的父亲序号,此时这个序号因为变成了5,所以我们就会输出5了。看到这肯定有人有疑问了,那万一之前5也输入过了呢?那么我们不妨来想一下,如果在输入第二个4之前输入过5,那么5的父亲节点是不是又变成6了,这时候我们再输入第二个4,会发现find(4)输出的是6,为什么呢?仔细观察find函数的终止条件是f[i]==i,也就是说此时find(4)输出的不会是5,因为f[5]已经不等于5了,变成6了,那么find函数会继续从5递归,然后递归到6发现f[6]==6,那么他就会返回6,意思也就是4的父亲序号是6,所以目前的父子关系是4->5->6,然后我们输出6之后还要记得将6的父亲序号变成7,这样关系变成4->5->6->7,因为之前通过输入454,我们得到的数组是456了,所以下一次如果有输入是4或者5或者6的话,就会根据父子关系输出7,达到不重复的作用~;

题解代码:

#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define ll long long
int f[N];
int find(int num)
{
    if (num != f[num])
    {
        f[num] = find(f[num]);
    }
    return f[num];
}
int main()
{
    int n;
    cin >> n;
    vector<int> num(n);
    for (int i = 0; i < N; ++i)
    {
        f[i] = i;
    }
    for (int i = 0; i < n; ++i)
    {
        cin >> num[i];
        cout << find(num[i]) << " ";
        f[find(num[i])]++;
    }
    return 0;
}

最后提醒一点吧,就是并查集f数组的大小是N也就是1000010,这个是根据题目描述来的,题目中输入的最大值是1000000,所以我们尽量开的比这个大一点,千万不要搞错了,开成了n(数组数量),这样会报错的哦~~~,因为一开始初始化每个数的爹都是自己,所以我输入一个99999,那肯定他的爹得是99999呀~ 

本题的收获:

本题利用的并查集的特性还是比较巧妙地,相信很多人也是第一次遇见,平时我们用并查集其实还是以合并和查找为主,本题没有用到合并,还是利用并查集中的父子关系,通过寻找父子关系的特性,将目前可以输出的数当成最终的父亲,输出后就将父亲加1。本题乍一看其实和哈希映射还有点像,学过数据结构的小伙伴可能会有点印象,在学哈希冲突的时候有类似的案例,其实本题楼主最开始想的时候考虑过用一个数组f[i]记录当输入为i时下一个不重复的元素,不过问题就是例如输入了4和5,f[i]此时变成了5,但是5已经被输入过了,不过这时候应该可以通过循环输出f[f[5]]也就是6实现,其实也有点类似本题中并查集的作用,本题中使用并查集倒是更方便一些~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值