HDU6949. Swap 图论 思维+并查集+分治
HDU6949. Swap
题意:
现有一个长度为\(N\)的整数序列,手上还有另外一个数\(H\)。
每一步,你可以将手上的数和序列当中的一个数交换。
问最少需要多少步使得序列单调不减。
分析:
仅自己的理解,说得不一定对。
考虑枚举最后一步做完后在手上的数。根据这个数我们可以判断出序列最终的样子,可以考虑计算最少经过多少步将序列变成这个样子。
考虑将每个位置要变成的数和这个位置原来的数连有向边(有多少重复就连多少重边),这样形成的图上的路径就是手上数变化的路径。可以预见会出现许多连通块,显然大小为\(1\)的连通块(自环)代表的那些位置不需要去动,直接忽视,设此时剩下的边数为\(k\)(也可以理解为有\(k\)个对应位置数不同)
在忽视大小为\(1\)的连通块后,此时会出现两种情况,
- 当最后做完后在手上的数和一开始在手上的数一样,且一开始序列中没有和手上的数一样的数时,此时所有的连通块都不含一开始手上的数,且每个连通块都形成了欧拉图。此时对于每个连通块,只能将手上的数替换连通块当中的一个数(耗费\(1\)次操作),然后耗费连通块中边数的次数,来变成最终的序列。最终花费为\(k+\text{连通块个数}\)。
- 除第\(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;
}