poj2299详解——归并排序+树状数组

博客详细介绍了 Ultra-QuickSort 算法,该算法通过相邻元素交换实现序列排序,并给出了求解逆序对数的方法。同时,讨论了归并排序法的递归合并思想,并强调在处理大规模数据时需要注意数据类型溢出问题。此外,文章还简单介绍了树状数组的概念及其在计算交换次数中的应用,通过比较排序前后树状数组的差异来确定所需交换操作的次数。

Ultra-QuickSort

Time Limit: 7000MS Memory Limit: 65536K
Total Submissions: 69564 Accepted: 26072

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

题意:一组无序数字,每每相邻的两个数互换,换多少次可以使其升序。其实本质是求逆序对数,逆序对就是位置在前但值小于位置在后的值,例如5,3,两两一组就是逆序对。为什么就是逆序对数?因为逆序对的两个数是一定有一次移动的,每次移动都是在逆转逆序对,不可能移动排好的或者相等的,那没有意义,把所有的逆序反过来就是结果了。反言之,求逆序对数就是互换的次数。

in.txt

5
9 1 0 5 4
3
1 2 3
0


归并排序法

思路:归并就是递归合并,把整个部分二分,然后每部分排好序,在合并插入。

首先理解两个排好序的数组,怎么合并?

举例:1,3,5,7    2,4,8,9

重头开始判断,哪个小放哪个,最后有两种情况,第一个数组先拍完或者第二个数组先排完,把剩下的塞后面就是了。

然后是怎么得到两个有序的数组呢,这就要递归和二分了,这个不好描述,代码更清晰。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>

using namespace std;
typedef long long ll;
const int N=5e5+10;
int a[N],arr[N];

ll Sort(int low,int mid,int high)
{
    int i=low,j=mid+1,k=low;
    ll ans=0;
    while(i<=mid&&j<=high){
	if(a[i]<=a[j])
	    arr[k++]=a[i++];
	else{
	    arr[k++]=a[j++];
	    ans+=j-k;
	}
    }
    while(i<=mid) arr[k++]=a[i++];
    while(j<=high) arr[k++]=a[j++];
    for(int i=low;i<=high;i++)
	a[i]=arr[i];
    return ans;
}
ll MergeSort(int low,int high)
{
    if(low>=high) return 0;
    ll sum=0;
    int mid=(low+high)/2;
    sum+=MergeSort(low,mid);
    sum+=MergeSort(mid+1,high);
    sum+=Sort(low,mid,high);
    return sum;
}
int main()
{
    //freopen("in.txt","r",stdin);
    int n;
    while(scanf("%d",&n)!=EOF&&n){
	for(int i=0;i<n;i++)
		scanf("%d",&a[i]);
	printf("%I64d\n",MergeSort(0,n-1));
	 /*
        for(int i=0;i<n;i++)
            printf("a[%d]=%d\n",i,a[i]);
        */
    }
    return 0;
}

这题要注意一个细节,本题sum最大的情况就是500,000的递减序列,这需要500000*500000/2,是比较大的,用int会出错,要用long long或者__int64。还有就是上述写法是求的移动次数。

 


树状数组

树状数组不怎么好理解,那我们就不理解它的难点,我们只要知道最基本的,树状数组能得到什么?

void build(int x)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tree[i]++;
}
int get_sum(int x)
{
    int res=0;
    for(int i=x;i>=1;i-=lowbit(i))
        res+=tree[i];
    return res;
}

撇开细节,这是最基本的树状数组代码,我的理解是把数据大小以二进制的思想排列,我们平时是以十进制的思想排列数组,如果不理解没关系,我也不是很懂,上面这样的代码是做了件这样的事:build把一个数据按顺序插入树中,get_sum判断一个数据在树中有多少个数据不大于它。这可以用暴力去理解,但无法用暴力的方法去做,会超时。

然后思考这个东西有什么用?要求的是交换次数,也就是说我们只要知道顺序,数据的值无所谓,容易得到排好序的结果:

9 1 0 5 4 ——> 0 1 4 5 9

我们保留原位置的标号,例如(9 1 0 5 4)的标号(1 2 3 4 5),排序后就是(5 2 1 4 3),再用上述的树状数组就会得到(1 1 1 3 3),而如果一开始的数据是排好的,如果输入的是(0 1 4 5 9),那么排序后标号(1 2 3 4 5),树状结果(1 2 3 4 5),差异就在这,很明显(1 2 3 4 5)就是排列好的结果,这与(1 1 1 3 3)的差值就是需要移动的次数。这还要记住一点,移动是不会改变其他逆序对的。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define lowbit(x) (x&(-x))
using namespace std;
const int N=5e5+10;
int num[N],tree[N];
int n;
ll sum=0;

struct node
{
    int value,sign;
}arr[N];

void build(int x)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tree[i]++;
}
int get_sum(int x)
{
    int res=0;
    for(int i=x;i>=1;i-=lowbit(i))
        res+=tree[i];
    return res;
}

bool cmp(node n1,node n2)
{
    return n1.value<n2.value;
}
int main()
{
    //freopen("in.txt","r",stdin);
    while(scanf("%d",&n)==1&&n){
        sum=0;
        memset(tree,0,sizeof(tree));
        for(int i=1;i<=n;i++){
            scanf("%d",&arr[i].value);
            arr[i].sign=i;
        }
        sort(arr+1,arr+n+1,cmp);
        for(int i=1;i<=n;i++)
            num[arr[i].sign]=i;
        for(int i=1;i<=n;i++){
            build(num[i]);
            //printf("num=%d get_sum=%d\n",num[i],get_sum(num[i]));
            sum+=i-get_sum(num[i]);
        }
        printf("%I64d\n",sum);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值