2025.04.28 CW 模拟赛 B. 冒泡排序
B. 冒泡排序
题目描述
下面是一段实现冒泡排序算法的 C++ 代码:
for (int i = 1; i <= n; ++i) {
    for (int j = 1; j <= n - i; ++j) {
        if (a[j] > a[j + 1]) {
            swap(a[j], a[j + 1]);
        }
    }
}
其中待排序的 \(a\) 数组是一个 \(1 \ldots n\) 的排列,\(\text{「swap」}\) 函数将交换数组中对应位置的值。
对于给定的数组 \(a\) 以及给定的非负整数 \(k\),使用这段代码执行了正好 \(k\) 次 \(\text{「swap」}\) 操作之后数组 \(a\) 中元素的值会是什么样的呢?
思路
首先, 当 \(k\) 大于总共需要的交换次数时, 也就是大于逆序对个数时一定无解.
接下来考虑 \(k\) 小于等于逆序对个数的情况. 我们将操作分为两个部分: 完整的几组冒泡排序和剩下的操作.
这样, 就有一个比较自然的思路: 首先求出最多有多少组完整的冒泡排序, 再求出过后的序列, 最后剩下的操作次数是 \(\mathcal{O}(n)\) 级别的, 直接暴力模拟即可.
先考虑完整的冒泡排序, 我们记 \(d_i = \sum_{j = 1}^{i - 1} [a_j > a_i]\), 也就是逆序对个数. 假设当前轮数为 \(x\), 则每一个数的交换次数就为 \(\min(x, d_i)\), 总交换数就为 \(\displaystyle \sum_{i = 1}^n \min(x, d_i)\). 这个式子满足单调性, 直接二分求出最后一个总和小于等于 \(k\) 的数就行了.
再来看如何求出完整 \(x\) 轮冒泡排序后的序列. 对于 \(d_i \ge x\), 它会到 \(i - x\) 的位置, 也就相当于左侧有 \(x\) 个比其大的数会越过它; 而对于 \(d_i < x\) 的数, 它们左侧已经没有比它们大的数了, 所以在剩下的位置中升序填入即可.
最后对于剩下的操作次数直接暴力模拟便可得出结果.
#include <iostream>
#include <algorithm>
#include <ranges>
#include <vector>
using namespace std;
typedef long long ll;
char buf[1 << 20], *p1, *p2;
#define getchar() (p1 == p2 and (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? 0 : *p1++)
int read() {
    int x = 0; char c = getchar();
    while (c < '0' or c > '9') {
        c = getchar();
    }
    while (c >= '0' and c <= '9') {
        x = x * 10 + (c & 15);
        c = getchar();
    }
    return x;
}
constexpr int N = 1000001;
int n, a[N], d[N], ans[N];
ll k, sum;
int tr[N];
void update(int x) {
    for (int i = x; i; i -= i & -i) {
        ++tr[i];
    }
}
int query(int x) {
    int res = 0;
    for (int i = x; i <= n; i += i & -i) {
        res += tr[i];
    }
    return res;
}
ll check(int x) {
    ll tot = 0;
    for (int i = 1; i <= n; ++i) {
        tot += min(x, d[i]);
    }
    return tot;
}
void init() {
    scanf("%d %lld", &n, &k);
    for (int i = 1; i <= n; ++i) {
        a[i] = read();
        d[i] = query(a[i]);
        sum += d[i], update(a[i]);
    }
}
void calculate() {
    if (k > sum) {
        puts("Impossible!");
        return;
    }
    int l = 0, r = n - 1, c = 0;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (check(mid) <= k) {
            l = mid + 1;
            c = mid;
        }
        else {
            r = mid - 1;
        }
    }
    k -= check(c);
    vector<int> lft;
    for (int i = 1; i <= n; ++i) {
        if (d[i] >= c) {
            ans[i - c] = a[i];
        }
        else {
            lft.push_back(a[i]);
        }
    }
    ranges::sort(lft);
    for (int i = 1, p = 0; i <= n; ++i) {
        !ans[i] and (ans[i] = lft[p++]);
    }
    for (int i = 1; i < n and k; ++i) {
        if (ans[i] > ans[i + 1]) {
            --k;
            swap(ans[i], ans[i + 1]);
        }
    }
    for (int i = 1; i <= n; ++i) {
        printf("%d ", ans[i]);
    }
}
void solve() {
    init();
    calculate();
}
signed main() {
    solve();
    return 0;
}
                    
                
                
            
        
浙公网安备 33010602011771号