题目

有一个长为 的排列 ,与
每对 表示可以将 的值与 的值互换。 的使用顺序与次数不限。
求任意次操作之后能得到的字典序最小的排列是什么?

输入
第三个参数为初始排列
第四个参数为

返回
字典序最小的排列。

解题思路

可以将每一个下标看作图中的一个节点,把互换的关系看作是连接两个节点的边,那么由于互换操作具有传递性,即如果下标 a 和 b 的值互换,下标 b 和 c 的值互换,则下标 a 和 c 的值也可以互换。也就是说,所有互换的下标属于同一个连通分量。因此,可以使用并查集来维护这种连通分量的关系。

首先遍历 ,构造并查集。可以互换值的两个下标属于同一个连通分量,因此将两个下标进行合并。

然后遍历所有的下标。将属于同一个连通分量的下标作为一个集合,分别对集合中的下标和值排序,并将其中较小的下标对应较小的值。

C++代码

/**
 * struct Point {
 *    int x;
 *    int y;
 * };
 */

class UnionFind {
    vector<int> parent;

public:
    UnionFind(int n){
        parent.resize(n, 0);
        iota(parent.begin(), parent.end(), 0);
    }

    int find(int i){
        if(i == parent[i]){
            return i;
        }
        parent[i] = find(parent[i]);
        return parent[i];
    }

    void unite(int x, int y){
        parent[find(x)] = find(y);
    }
};

class Solution {
public:
    /**
     * 返回牛牛所能得到的字典序最小的排列
     * @param n int整型 
     * @param m int整型 
     * @param perm int整型vector 
     * @param Pair Point类vector 
     * @return int整型vector
     */
    vector<int> solve(int n, int m, vector<int>& perm, vector<Point>& Pair) {
        // write code here
        UnionFind uf(n);
        for(int i=0; i<m; ++i){
            uf.unite(Pair[i].x - 1, Pair[i].y - 1);
        }
        vector<vector<int>> vi(n);
        vector<vector<int>> va(n);
        for(int i=0; i<n; ++i){
            vi[uf.find(i)].push_back(i);
            va[uf.find(i)].push_back(perm[i]);
        }
        for(int i=0; i<n; ++i){
            sort(vi[i].begin(), vi[i].end());
            sort(va[i].begin(), va[i].end());
            for(int j=0; j<vi[i].size(); ++j){
                perm[vi[i][j]] = va[i][j];
            }
        }
        return perm;
    }
};