HDU6949. Swap 图论 思维+并查集+分治

HDU6949. Swap

题意:

现有一个长度为\(N\)的整数序列,手上还有另外一个数\(H\)​。

每一步,你可以将手上的数和序列当中的一个数交换。

问最少需要多少步使得序列单调不减。

分析:

仅自己的理解,说得不一定对。

考虑枚举最后一步做完后在手上的数。根据这个数我们可以判断出序列最终的样子,可以考虑计算最少经过多少步将序列变成这个样子。

考虑将每个位置要变成的数和这个位置原来的数连有向边(有多少重复就连多少重边),这样形成的图上的路径就是手上数变化的路径。可以预见会出现许多连通块,显然大小为\(1\)的连通块(自环)代表的那些位置不需要去动,直接忽视,设此时剩下的边数为\(k\)​​(也可以理解为有\(k\)个对应位置数不同)

在忽视大小为\(1\)的连通块后,此时会出现两种情况,

  1. 当最后做完后在手上的数和一开始在手上的数一样,且一开始序列中没有和手上的数一样的数时,此时所有的连通块都不含一开始手上的数,且每个连通块都形成了欧拉图。此时对于每个连通块,只能将手上的数替换连通块当中的一个数(耗费\(1\)​次操作),然后耗费连通块中边数的次数,来变成最终的序列。最终花费为\(k+\text{连通块个数}\)​​。
  2. 除第\(1\)​种情况外的所有情况。此时所有的连通块中,有\(1\)​​​​​个连通块形成了以一开始在手上的数为起点,以最后在手上的数为终点的半欧拉图。其他所有连通块(也可以没有),形成了欧拉图。此时对于形成半欧拉图的那个连通块,我们可以耗费这个连通块中边数的次数,对于其他连通块,将手上的数替换连通块当中的一个数,然后再耗费连通块中边数的次数,来变成最终的序列。最终花费为\(k+\text{连通块个数}-1\)​。​

此时问题就成为了维护大小大于\(1\)​​的连通块个数的问题。可以使用可按加边顺序撤消的并查集+分治\(O(n\log^2n)\)的时间复杂度下解决。

细节还是有些坑的,懒得写了……,以后有机会再补。

代码:

#include <algorithm>
#include <cstdio>
#include <unordered_map>
using namespace std;
const int maxn = 3e5 + 10;
struct DSU {
    int fa[maxn], sz[maxn], top;
    int num;
    pair<int, int> stk[maxn];
    void init(int n) {
        for (int i = 1; i <= n; i++)
            fa[i] = i, sz[i] = 1;
        top = num = 0;
    }
    int find(int x) {
        if (x == fa[x])
            return x;
        return find(fa[x]);
    }
    bool merge(int u, int v) {
        u = find(u), v = find(v);
        if (u == v)
            return 0;
        if (sz[u] > sz[v])
            swap(u, v);
        if (sz[u] == 1 && sz[v] == 1) {
            num++;
        } else if (sz[u] != 1 && sz[v] != 1) {
            num--;
        }
        fa[u] = v;
        sz[v] += sz[u];
        stk[top++] = {u, v};
        return 1;
    }
    void rewind(int x) {
        while (top > x) {
            auto now = stk[--top];
            int u = now.first, v = now.second;
            fa[u] = u;
            sz[v] -= sz[u];
            if (sz[u] == 1 && sz[v] == 1) {
                num--;
            } else if (sz[u] != 1 && sz[v] != 1) {
                num++;
            }
        }
    }
    bool is_single(int x) { return fa[x] == x && sz[x] == 1; }
} dsu;
int n, h, ans;
int a[maxn], b[maxn];
unordered_map<int, int> M;
void work(int l, int r, int k) {
    if (l == r) {
        int tmp = dsu.is_single(M[h]);
        ans = min(ans, k + dsu.num - 1 + tmp);
        return;
    }
    int mid = l + r >> 1;
    int o_top = dsu.top;
    int n_k = k;
    for (int i = n - r + 1; i <= n - mid; i++) {
        if (a[i] != b[i]) {
            dsu.merge(M[a[i]], M[b[i]]);
            n_k++;
        }
    }
    work(l, mid, n_k);
    dsu.rewind(o_top);
    n_k = k;
    for (int i = n - l; i >= n - mid; i--) {
        if (a[i] != b[i + 1]) {
            dsu.merge(M[a[i]], M[b[i + 1]]);
            n_k++;
        }
    }
    work(mid + 1, r, n_k);
    dsu.rewind(o_top);
}
void solve() {
    int tot = 0;
    M.clear();
    for (int i = 1; i <= n; i++) {
        scanf("%d", a + i);
        b[i] = a[i];
    }
    b[n + 1] = h;
    sort(b + 1, b + n + 2);
    for (int i = 1; i <= n + 1; i++) {
        if (!M.count(b[i]))
            M[b[i]] = ++tot;
    }
    dsu.init(tot);
    ans = 0x7fffffff;
    work(0, n, 0);
    printf("%d\n", ans);
}
int main() {
    while (scanf("%d%d", &n, &h) == 2) {
        solve();
    }
    return 0;
}
posted @ 2021-08-30 00:24  聆竹听风  阅读(54)  评论(0)    收藏  举报