您的位置:首页 > 产品设计 > UI/UE

POJ 2299 Ultra-QuickSort 线段树

2016-04-21 17:23 459 查看
Ultra-QuickSort

Time Limit: 7000MS Memory Limit: 65536K
Total Submissions: 52842 Accepted: 19378
Description


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

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

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



我们先将原数组每个值附上一个序号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 数据结构 线段树