AtCoder Beginner Contest 233 F - Swap and Sort(并查集、思维)

F - Swap and Sort

题目大意:

给出一个 permutation,并给出 \(m\) 组关系 \((a_i, b_i)\) ,每次操作可以交换 \(P_{a_i}, P_{b_i}\),问能否在 5e5 次操作之内将 permutation 变为升序序列,若能则输出交换次数和交换步骤。

思路:

若不需要输出交换步骤,这道题就很简单了。

我们可以发现,将交换关系看作一条无向边的话,处于同一个连通块内的数字可以任意交换,那么连通块内部一定能构成有序序列。这样一来,我们可以用优先队列维护每一个连通块的最小值,依次填上去看是否能构成升序序列即可。

在本题中需要输出交换步骤。注意到 \(n\) 的范围很小,那么考虑类似冒泡排序的交换,在每一轮交换中找度为 1 的点(下标)需要放的数在哪(下标)。为什么可以这样交换呢?因为每一轮交换都能够让度为1的点(下标)放在升序序列中该放的数,放完后不会参与和影响后面的操作,即无后效性。这样暴力的交换最坏情况即为降序序列,交换次数为 \(999 + 998 + \dots + 1 = 499500\),满足题目限制。

具体实现上,我们可以使用并查集维护连通关系,每次交换暴力找到要交换的双方,dfs 去找交换路径即可。

Code:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const double eps = 1e-6;
const int N = 1e6 + 7;
//#define N 10
const int INF = 0x3f3f3f3f;
const int mod = 1000000007; //998244353
const int dir[8][2] = {0, 1, 0, -1, 1, 0, -1, 0,/* dir4 */ -1, -1, -1, 1, 1, -1, 1, 1};
ll gcd(ll a, ll b) { return b == 0 ? a : gcd(b, a % b); }
ll powmod(ll a, ll b) { ll res = 1; a %= mod; assert(b >= 0); for (; b; b >>= 1) { if (b & 1) res = res * a % mod; a = a * a % mod; } return res; }
template <class T> bool ckmin(T &a, const T &b) { return b < a ? a = b, 1 : 0; }
template <class T> bool ckmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
#define debug(a) cerr << "## " << #a << " = " << a << endl;

class Dsu {
public:
    static const int MAXN = 1e4 + 7;
    int fa[MAXN], rk[MAXN];
    void init(int n) {
        for (int i = 1; i <= n; i++) {
            fa[i] = i, rk[i] = 1;
        }
    }
    int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
    bool merge(int x, int y) {
        x = find(x), y = find(y);
        if (x == y) {
            return 0;
        }
        if (rk[x] >= rk[y]) {
            fa[y] = x;
        } else {
            fa[x] = y;
        }
        if (rk[x] == rk[y] && x != y)
            rk[x]++;
        return 1;
    }
    bool isSame(int x, int y) { return find(x) == find(y); }
} dsu;

int main() {
    ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    vector<int> p(n + 1);
    dsu.init(n);
    for (int i = 1; i <= n; i++) {
        cin >> p[i];
    }
    int m;
    cin >> m;

    vector<vector<pair<int, int>>> g(n + 1);
    vector<int> deg(n + 1);

    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        if (dsu.merge(a, b)) {
            g[a].emplace_back(b, i + 1);
            g[b].emplace_back(a, i + 1);
            deg[a]++;
            deg[b]++;
        }
    }

    vector<int> path;

    function<bool(int, int, int)> dfs = [&](int u, int fa, int target) {
        if (u == target) {
            return true;
        }
        for (auto [v, id] : g[u]) {
            if (v == fa) {
                continue;
            }
            if (dfs(v, u, target)) {
                path.emplace_back(id);
                swap(p[u], p[v]);
                return true;
            }
        }
        return false;
    };

    for (int round = 1; round <= n; round++) {
        for (int i = 1; i <= n; i++) {
            if (deg[i] == 1) {
                int target = -1; //下标,找度为1的点需要放的数在哪(注意是permutation
                for (int j = 1; j <= n; j++) {
                    if (p[j] == i) {
                        target = j;
                        break;
                    }
                }
                if (target == -1 || dfs(i, -1, target) == false) {
                    cout << -1 << "\n";
                    return 0;
                }
                deg[i]--;
                for (auto [v, id] : g[i]) {
                    deg[v]--;
                }
                break;
            }
        }
    }

    cout << (int)path.size() << "\n";
    for (int i = 0; i < (int)path.size(); i++) {
        cout << path[i] << " \n"[i == (int)path.size() - 1];
    }
    return 0;
}
posted @ 2022-03-04 13:51  Nepenthe8  阅读(120)  评论(0编辑  收藏  举报