Ultra-QuickSort produces the output
Your task is to determine how many swap operations Ultra-QuickSort needs to perform in order to sort a given input sequence.
5 9 1 0 5 4 3 1 2 3 0Sample Output
6 0
题意:给你个序列求逆序数;
思路:可归并,可树状数组求,可线段树求;
方法一:数状数组求逆序数;
为什么树状数组可以求逆序数?
你想想树状数组是不是可以求一个a数组 前i(1<=i<=n)和,记做a[1]+...a[i];
你想想是不是在函数 add(int x,int val) 传值时,x是从1~n 按顺序传的值;
而求逆序数时,因为给出的n个值太大;所以要经离散化 重新赋值为 1~n , 而生成的序列
和原来序列值不一样,但序列之间数的大小关系是相同;
因为数组树状可以知道在插入这个数之前,它前面的数(也就是小于它的数)是否插入过;
所以求出来经离散化序列中 它前面小于它的数的数量,再用当前位置处前面的总数量减去这个数;
就是 当前位置 前大于它的数的个数;
用树状数组求逆序数时,你一定要理解下面两个:
1、当给出的n个数,范围小时,不用用离散化; 离散化只是为了把数值范围缩小,区别是,可能原序列中有相同的数字,但离散化过后,所有的数字没有重复, 这对求逆序数没有影响;
2、(1)用树状数组求逆序数可以有重复的;我一开始以为不能有重复的,当时是我理解错了,可以有重复的;解释:
原序列:2 1 1
编号: 1 2 3
排序后:1 1 2
原编号:2 3 1 经离散化的a数组中的数:
a[2] = 1; a[3] = 2; a[1] = 3;
a = { 3 , 1, 2};
可以看出:原序列中的两个 1 ,第一个不变,还是1,第二个变成 2, 所以逆序数还是 2 ,不变;
(2) 就算不离散化 原序列 2 1 1,用树状数组求 也是 2;
看这句话:
res += (i - ssum(a[i])); // i减去前面小于等于x的个数,就是大于x的个数;
不光 减去小于x的个数和自身一个,还有 把所有等于x的个数也减去了;
自己模拟一边好好理解上面 说的话,一定要理解,不然你就算提交对了,还是不理解;
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
int n;
#define Max 500050
long long a[Max],c[Max];
struct node
{
long long val;
long long h;
}stu[Max];
long long cmp(node a,node b)
{
return a.val<b.val;
}
long long lowbit(long long i) // i 的二进制最后一个1的位置;
{
return i&-i;
}
void add(long long x,long long val) // 一般输入时,直接添加就是,但一定要理解lowbit()的意义;
{
while(x<=n)
{
c[x] += val;
x = x + lowbit(x);
}
}
// 返回这个ssum表示 x 前面小于等于x的个数和;
long long ssum(long long x) // x 的二进制最后一个x的位置,一直相减;知道x变为0位置,为了求从1~x的之间的总和;
{ // 因为 x 是经过离散化后的数,所以直接说求的1~x之间的和;
long long sum = 0;
while(x>0) // 这是大于0;
{
sum += c[x];
x = x - lowbit(x);
}
return sum;
}
int main()
{
long long i,j;
while(~scanf("%lld",&n)&&n)
{
for(i = 1;i<=n;i++)
{
scanf("%lld",&stu[i].val);
stu[i].h = i;
}
sort(stu+1,stu+(n+1),cmp);
for(i = 1;i<=n;i++) // 离散化;
a[stu[i].h] = i;
memset(c,0,sizeof(c));
long long res = 0;
for(i = 1;i<=n;i++)
{
add(a[i],1);
res += (i - ssum(a[i])); // i减去前面小于等于x的个数,就是大于x的个数;
}
printf("%lld\n",res);
}
return 0;
}
方法二:
线段树求逆序数;
一开始看到网上说线段树求逆序数;我实在不会写,在我心里面,好像线段树就是 更新 某区间 或者 某位置的值,还有就是查询某区间的和,最大值,最小值,实在想不出它和求逆序数有什么关系;
看到树状数组不就一边插入 一边计算前面小于x的个数吗;
记住 线段树更新的是位置,某个位置上的值;
方法:
先线段树数组全部节点初始化为0;
定义一个结构体,有值,有下标,按值从大到小排序,
开始:9 1 0 5 4
编号:1 2 3 4 5
排序之后
9 5 4 1 0
编号 1 4 5 2 3
再从前到后 插入更新线段树,注意是更新的线段树中的位置stu[i].pos 的值,利用回溯再改变其他值,每插入更新一次,就找区间[1,stu[i].pos-1] 中已经 插入值的个数, 这个个数就是 在stu[i].val之前 大于stu[i].val的数的个数;
代码:
// 线段树求逆序数 每插入更新一个节点,就查询一次;
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define Max 500050
struct node
{
int val;
int pos;
}stu[Max];
int tree[Max<<2];
int n;
int cmp(node a ,node b)
{
return a.val>b.val;
}
void build(int root,int star,int end)
{
if(star==end)
{
tree[root] = 0;
return ;
}
int mid = (star+end)>>1;
build(root*2,star,mid);
build(root*2+1,mid+1,end);
tree[root] = 0;
}
void updet(int root,int star,int end,int pp)
{
if(star==end)
{
tree[root]+=1;
return ;
}
int mid =(star+end)>>1;
if(pp<=mid) updet(root*2,star,mid,pp);
else updet(root*2+1,mid+1,end,pp);
tree[root]+=1;
}
int qur(int root,int star,int end,int l,int r)
{
if(l<=star&&r>=end)
{
return tree[root];
}
int mid = (star+end)>>1;
int sum = 0;
if(l<=mid) sum +=qur(root*2,star,mid,l,r);
if(r>mid) sum +=qur(root*2+1,mid+1,end,l,r);
return sum;
}
int main()
{
int i,j;
while(~scanf("%d",&n)&&n)
{
for(i = 1;i<=n;i++)
{
scanf("%d",&stu[i].val);
stu[i].pos = i;
}
sort(stu+1,stu+(n+1),cmp); //按值从小到大排序;
build(1,1,n);
long long res = 0;
for(i = 1;i<=n;i++)
{
updet(1,1,n,stu[i].pos); //记住线段树是 更新的是固定位置节点的值;
if(stu[i].pos==1)
continue;
res += qur(1,1,n,1,stu[i].pos-1);
}
printf("%lld\n",res);
}
return 0;
}
方法三:归并排序
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
#define Max 500050
int n;
long long ans;
int a[Max];
int b[Max];
void solve(int a[],int b[],int star,int mid,int end) // 归并排序,把a[shar...mid]和a[mid+1...end],
{ // 两个有序的序列合并成 1 个,合并到b数组对应的位置;
int i = star;
int j = mid + 1;
int k = star;
while(i<=mid&&j<=end)
{
if(a[i]<=a[j])
{
b[k++] = a[i++];
}
else
{
b[k++] = a[j++];
ans += mid - i +1; // a[j] 数前面有几个大于自己的数;
}
}
while(i<=mid)
b[k++] = a[i++];
while(j<=end)
b[k++] = a[j++];
}
void mergersort(int a[],int b[],int star,int end)
{
if(star<end)
{
int mid = (star+end)>>1;
mergersort(b,a,star,mid); // 左数组 左右数组相邻
mergersort(b,a,mid+1,end); // 右数组 为什么每一次调用都把a,b 数组的位置都换位置?
solve(a,b,star,mid,end); // 若是合并a数组中的两个序列时,就是按a数组排,按顺序排好,赋于b数组中
} // 可以认为这是b数组的左数组(子树),b的右数组可以通过右递归完成;这时候b中的两个数组是有序的,所以这时候要按
} //b 数组排,合并的a数组中;就这样,谁是有序的就按谁排,并赋予的另一个数组中;交替有序;所以每次递归a,b数组都要交换位置;
int main()
{
int i,j;
while(~scanf("%d",&n)&&n)
{
ans = 0;
for(i=0;i<n;i++)
{
scanf("%d",&a[i]);
b[i] = a[i];
}
mergersort(a,b,0,n-1);
printf("%lld\n",ans);
}
return 0;
}