POJ 2299 【树状数组 离散化】

题目链接:POJ 2299 Ultra-QuickSort

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

大概题意:

给一串长度为N的序列, 问要经过多少次调换才能形成一个升序。

大致思路:

记录元素的大小及坐标, 因为要形成升序,前面大的元素要交换到后面,所以就是转换成了求一段序列的逆序数。

这道题很容易想到冒泡排序法暴力,复杂度O(N^2) ,但这道题看到有求逆序数,优先用树状数组或线段树,如果用树状数组,效率会快很多很多。

之前写过一篇用树状数组求逆序数的方法,可以参考一下:链接

如果用树状数组,那么问题就来了,a[ i ] 的范围【0, 999999999】, 数据范围有点大,而且通过样例可以发现, 数据也不是连续的,所以按数据范围开数组这个太恶心了,我们不妨做一下离散化的处理,之后求逆序数就可以交给树状数组啦。

 

1、为什么要离散化?

正如前面所说的a[ i ]的范围最大可以去到 999,999,999. 如果给一组的一组数据是   999999999, 1, 0, 5,3 . 就5个数, 但树状数组的范围就要开到【0, 999999999】, 造成了大量的内存浪费,而且题目也不允许,这笔买卖可不划算。所以我们要用把这五个数离散化一下。

2、如何离散化?

记录他们的下标,用下标来搞事情。

既然要同时记录下标和数值,我们可以考虑两种映射

①map, 效率有点低

②定义一个结构体 v用于记录数值(排序用), no用于记录下标。

struct node

{

  int v, no;

};

输入序列之后,按升序排序,这样我们就可以得到一个按元素升序排好序表示每个元素出现的顺序的序列。

举个栗子: 999999999, 1, 0, 5, 3

t.v9...91053
t.no12345
t.v (排序后)01359...9
t.no(排序后)32541

 



 

很显然,我们就可以发现排序后的 t.no 序列的逆序数就是需要交换的次数,例如9...9 的 下表为 1 ,它前面 t.no 比它大的数有四个, 说明在原序列(未排序)中 9...9的后面(因为下标也代表出现的顺序,越大越靠后)比 9...9 小(因为排序)的有四个。所以问题到这里就可以交给树状数组去解决啦!

AC code:

///poj 2299 树状数组 离散化 求逆序数
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF ox3f3f3f3f
using namespace std;

const int MAXN = 500005;

struct node
{
    int v;
    int no;
}c[MAXN];

int aa[MAXN];
int N;

bool cmp(struct node a, struct node b)
{
    return a.v < b.v;
}

int lowbit(int i)
{
    return i&(-i);
}

void add(int i, int value)
{
    while(i <= N)
    {
        aa[i]+=value;
        i+=lowbit(i);
    }
}

int sum(int i)
{
    int res = 0;
    while(i > 0)
    {
        res+=aa[i];
        i-=lowbit(i);
    }
    return res;
}

int main()
{
    while(scanf("%d", &N) != EOF && N)
    {
        memset(aa, 0, sizeof(aa));
        for(int i = 1; i <= N; i++)
        {
            scanf("%d", &c[i].v);
            c[i].no = i;
        }
        sort(c+1, c+N+1, cmp);
        long long int ans = 0;
        for(int i = 1; i <= N; i++)
        {
            add(c[i].no, 1);
            ans += i-sum(c[i].no);
        }
        printf("%lld\n", ans);
    }
    return 0;
}
View Code

 

当然如果这样子你还是没办法理解,那我们可以在进一步转换,通过数组 aa[ ], 还原回原本的序列结合冒泡排序的思想进行求解

t.v01359...9
t.no32541
aa[ t.no ]12345
aa[1] ~ aa[5]52143

 

 

 

 

很明显,这里的aa[ i ] 表示的是第 i 个元素在整个序列中是第 aa[ i ] 大。那么结合冒泡排序的思想,这里的逆序数表示的是当前元素转到指定位置需要swap多少次

it[1]t[2]t[3]t[4]t[5]i - sum( aa[i] )
1000011 - 1 = 0
210012 - 1 = 1
3110013 - 1 = 2
4110114 - 3 = 1
5111115 - 3 = 2

 

 

 

 

 

 

例如, 按照冒泡排序法,第二个数 1 要跟 9... 9 交换位置一次, 序列变成 1,9...9, 0, 5, 3

                                         第三个数 0 要跟前面两个数交换位置,所以交换次数为2, 序列变成 0,1,9...9,5,3

                                         第四个数 5 要跟前面的 9...9 交换位置,交换次数为1,序列 0,1,5,9...9, 3

                                         第五个数 3 要跟前面两个数交换位置,交换位置为2, 序列 0,1,3,5,9...9

答案就是:0+1+2+1+2 = 6;

 

AC code:

///poj 2299 树状数组 离散化 求逆序数
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define INF ox3f3f3f3f
using namespace std;

const int MAXN = 500005;

struct node
{
    int v;
    int no;
}c[MAXN];

int aa[MAXN];
int t[MAXN];
int N;

bool cmp(struct node a, struct node b)
{
    return a.v < b.v;
}

int lowbit(int i)
{
    return i&(-i);
}

void add(int i, int value)
{
    while(i <= N)
    {
        t[i]+=value;
        i+=lowbit(i);
    }
}

int sum(int i)
{
    int res = 0;
    while(i > 0)
    {
        res+=t[i];
        i-=lowbit(i);
    }
    return res;
}

int main()
{
    while(scanf("%d", &N) != EOF && N)
    {
        memset(t, 0, sizeof(t));
        for(int i = 1; i <= N; i++)
        {
            scanf("%d", &c[i].v);
            c[i].no = i;
        }
        sort(c+1, c+N+1, cmp);

        for(int i = 1; i <= N; i++)
            aa[c[i].no] = i;

        long long int ans = 0;
        for(int i = 1; i <= N; i++)
        {
            add(aa[i], 1);
            ans += i-sum(aa[i]);
        }
        printf("%lld\n", ans);
    }
    return 0;
}
View Code

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
POJ 2182是一道使用树状数组解决的题目,题目要求对给定的n个数进行排序,并且输出每个数在排序后的相对位置。树状数组是一种用来高效处理前缀和问题的数据结构。 根据引用中的描述,我们可以通过遍历数组a,对于每个元素a[i],可以使用二分查找找到a到a[i-1]中小于a[i]的数的个数。这个个数就是它在排序后的相对位置。 代码中的query函数用来求前缀和,add函数用来更新树状数组。在主函数中,我们从后往前遍历数组a,通过二分查找找到每个元素在排序后的相对位置,并将结果存入ans数组中。 最后,我们按顺序输出ans数组的元素即可得到排序后的相对位置。 参考代码如下: ```C++ #include <iostream> #include <cstdio> using namespace std; int n, a += y; } } int main() { scanf("%d", &n); f = 1; for (int i = 2; i <= n; i++) { scanf("%d", &a[i]); f[i = i & -i; } for (int i = n; i >= 1; i--) { int l = 1, r = n; while (l <= r) { int mid = (l + r) / 2; int k = query(mid - 1); if (a[i > k) { l = mid + 1; } else if (a[i < k) { r = mid - 1; } else { while (b[mid]) { mid++; } ans[i = mid; b[mid = true; add(mid, -1); break; } } } for (int i = 1; i <= n; i++) { printf("%d\n", ans[i]); } return 0; } ``` 这段代码使用了树状数组来完成题目要求的排序功能,其中query函数用来求前缀和,add函数用来更新树状数组。在主函数中,我们从后往前遍历数组a,通过二分查找找到每个元素在排序后的相对位置,并将结果存入ans数组中。最后,我们按顺序输出ans数组的元素即可得到排序后的相对位置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [poj2182Lost Cows——树状数组快速查找](https://blog.csdn.net/aodan5477/article/details/102045839)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [poj_2182 线段树/树状数组](https://blog.csdn.net/weixin_34138139/article/details/86389799)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值