poj 2299 Ultra-QuickSort(归并排序/树状数组/线段树)

题目链接:http://poj.org/problem?id=2299

题意

给出含有 $n$ 个不同的数的数组,相邻元素间可以两两交换,计算数组变为升序需要交换多少次。

题解

其实就是计算数组中的逆序数

题解一(归并排序)

考虑两个区间合并的过程,如果左边区间未归并数首部的值大于右边区间未归并数首部的值(以下简称左首部、右首部),那么左首部及其之后的数都与右首部构成逆序。

代码

#include <cstdio>
#define ll long long
const int N = 5e5 + 100;

int n, a[N], b[N];
ll ans;

void merge(int start, int mid, int end) {
    int i = start, j = mid + 1, k = start;
    while (i <= mid && j <= end) {
        if (a[i] <= a[j]) b[k++] = a[i++];
        else {
            ans += mid - i + 1; //原写法为 j - k,感觉 mid - i + 1 更好理解些
            b[k++] = a[j++];
        }
    }
    while (i <= mid) b[k++] = a[i++];
    while (j <= end) b[k++] = a[j++];
    for (int i = start; i <= end; i++) a[i] = b[i];
}

void MergeSort(int l, int r) {
    if (l >= r) return;
    int mid = (l + r) / 2;
    MergeSort(l, mid);
    MergeSort(mid + 1, r);
    merge(l, mid, r);
}

int main() {
    while (scanf("%d", &n) && n != 0) {
        ans = 0;
        for (int i = 0; i < n; i++) scanf("%d", &a[i]);
        MergeSort(0, n - 1);
        printf("%lld\n", ans);
    }
}

题解二(树状数组)

假设每个数的位置都有 $1$ 盏灯,从小到大熄灭每个数的灯,那么每个数的逆序数即为访问它时它的左边仍亮着的灯的个数。问题即化为单点修改,区间求和。

代码

#include <cstdio>
#include <utility> // pair
#include <algorithm> //sort
#define lowbit(x) (x & (-x))
#define ll long long
using namespace std;
const int N = 5e5 + 100;

int n, bit[N];
pair<int, int> pr[N];

void update(int pos, int val) {
    for (int i = pos; i <= N; i += lowbit(i))
        bit[i] += val;
}

int query(int pos) {
    int res = 0;
    for (int i = pos; i >= 1; i -= lowbit(i))
        res += bit[i];
    return res;
}

int main() {
    while (scanf("%d", &n) && n != 0) {
        for (int i = 1; i <= n; i++) {
            int x; scanf("%d", &x);
            pr[i] = {x, i};
        }
        sort(pr + 1, pr + 1 + n);
        for (int i = 1; i <= n; i++)
            bit[i] = lowbit(i); //开始时每个位置的灯都为 1,所以 bit[i] 的值为它所管辖的位置的个数,等价于执行 n 次 update(i, 1)
        ll ans = 0;
        for (int i = 1; i <= n; i++) {
            update(pr[i].second, -1);
            ans += query(pr[i].second - 1);
        }
        printf("%lld\n", ans);
    }
}

拓展

可以考虑下如何用线段树实现,以及当数组需要交换为降序时代码应如何改动。

参考资料

https://www.runoob.com/w3cnote/merge-sort.html(归并排序过程的动图演示超赞)

https://blog.csdn.net/morewindows/article/details/6678165/(白话经典算法系列超赞)

https://www.cnblogs.com/handsomecui/p/4804070.html(归并排序)

https://blog.csdn.net/jk_chen_acmer/article/details/79347003(逆序数、树状数组)

 

posted @ 2020-05-28 17:06  Kanoon  阅读(156)  评论(0编辑  收藏  举报