POJ 2299 Ultra-QuickSort 线段树

Ultra-QuickSort
Time Limit: 7000MS   Memory Limit: 65536K
Total Submissions: 52842   Accepted: 19378

Description

POJ 2299 Ultra-QuickSort 线段树_第1张图片In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a sequence of n distinct integers by swapping two adjacent sequence elements until the sequence is sorted in ascending order. For the input sequence 
9 1 0 5 4 ,
Ultra-QuickSort produces the output 
0 1 4 5 9 .
Your task is to determine how many swap operations Ultra-QuickSort needs to perform in order to sort a given input sequence.

Input

The input contains several test cases. Every test case begins with a line that contains a single integer n < 500,000 -- the length of the input sequence. Each of the the following n lines contains a single integer 0 ≤ a[i] ≤ 999,999,999, the i-th input sequence element. Input is terminated by a sequence of length n = 0. This sequence must not be processed.

Output

For every input sequence, your program prints a single line containing an integer number op, the minimum number of swap operations necessary to sort the given input sequence.

Sample Input

5
9
1
0
5
4
3
1
2
3
0

Sample Output

6
0

Source

Waterloo local 2005.02.05

点击打开题目链接 POJ 2299

将一组各不相同的数升序排列,每次只能交换相邻的两个数,求最小的交换次数。

这里用线段树解决,参考博客:点击打开博客链接

POJ 2299 Ultra-QuickSort 线段树_第2张图片

我们先将原数组每个值附上一个序号index,再将它排序。如题目的例子:

num:    9  1  0  5  4

index:  1  2  3  4  5

排序后:

num:    0  1  4  5  9

index:  3  2  5  4  1

由于排序后num为0的点排在原来数组的第3个,为了将它排到第一个去,至少需要向前移动两次,它也等价于最小的数0之前有2个数比它大(所以要移动两次),将0移到它自己的位置后,我们将0删掉(目的是为了不对后面产生影响)。再看第二大的数1,它出现在原数组的第二个,他之前有一个数比它大所以需要移动一次。这样一直循环下去那么着5个数所需要移动的次数就是:

num:   0  1  4  5  9

次数      2  1  2  1  0

将次数全部要加起来就是最后所需要移动的总次数。

在建一棵树时,不是直接将原来的num放进树里面,而是将它的下标放进树里面,最初每个节点上赋值为1.然后当查找第一个num时,由于是找的下标为3的位置,所以我们就直接找区间[1,3)之间有多少个1(就是求前导和),这里面1的个数就是第一个num=0所要移动的次数,然后我们把0去掉,其实也就是把下标为3的那个1去掉。这样每个值就依次计算出来了。

当然其实只要是想明白了,不用线段树,直接用树状数组写起来会简便很多。(因为每次只需要计算前导和以及去掉某一个点,是对点的操作)。

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAXN = 500000 + 10;
int total[MAXN << 2], num[MAXN], index[MAXN];
int N;
int ql, qr;
//ql,qr代表查询区间的左右端点
//查询 [ql , qr] 前导和,o是当前结点编号,L和R是当前结点的左右端点
int query(int o, int L, int R)
{
    int M = L + (R - L) / 2, ans = 0;
    if (ql <= L && qr >= R) return total[o]; //当前结点完全包含在查询区间内
    if (ql <= M) ans += query(2 * o, L, M);     //往左走,查询左子树
    if (M < qr) ans += query(2 * o + 1, M + 1, R);  //往右走,查询右子树
    return ans;
}

int p, v;       //代表修改点的位置和要修改的数值
void update(int o, int L, int R)
{
    total[o] += v;          //更新树
    if (L == R) return;
    int M = L + (R - L) / 2;
    if (p <= M) update(2 * o, L, M);    //递归更新左子树或者右子树
    else update(2 * o + 1, M + 1, R);
}
//给下标排序的函数
bool cmp(int x, int y)
{
    return num[x] < num[y];
}

int main()
{
    while (~scanf("%d", &N), N)
    {
        memset(total, 0, sizeof(total));
        for (int i = 1; i <= N; i++)
        {
            scanf("%d", &num[i]);
            p = i;          //初始化每个结点赋值1,并更新树
            v = 1;
            update(1, 1, N);
            index[i] = i;   //记录下标
        }

        sort(index + 1, index + N + 1, cmp);    //将下标按num的大小排序
        long long ans = 0;
        for (int i = 1; i <= N; i++)
        {
            ql  = 1;        //查询区间[ql, qr] <=> [1, index[i]]
            qr = index[i];
            ans += (query(1, 1, N) - 1);    //当前位置不算交换一次,减一
            p = index[i];           //查询后就将这个数去掉
            v = -1;
            update(1, 1, N);
        }
        printf("%lld\n", ans);
    }
    return 0;
}

树状数组解决: 点击打开博客链接树状数组解决

你可能感兴趣的:(数据结构,线段树,ACM)