题目描述
在这个问题中,您必须分析特定的排序算法----超快速排序。
该算法通过交换两个相邻的序列元素来处理n个不同整数的序列,直到序列按升序排序。
对于输入序列
9 1 0 5 4
超快速排序生成输出
0 1 4 5 9
。
您的任务是确定超快速排序需要执行多少交换操作才能对给定的输入序列进行排序。
输入格式
输入包括一些测试用例。
每个测试用例的第一行输入整数n,代表该用例中输入序列的长度。
接下来n行每行输入一个整数$a_i$,代表用例中输入序列的具体数据,第i行的数据代表序列中第i个数。
当输入用例中包含的输入序列长度为0时,输入终止,该序列无需处理。
输出格式
对于每个需要处理的输入序列,输出一个整数op,代表对给定输入序列进行排序所需的最小交换操作数,每个整数占一行。
数据范围
$0≤N<500000$
$0≤ai≤999999999$
样例
输入样例:
5
9
1
0
5
4
3
1
2
3
0
输出样例:
6
0
归并排序求逆序对
首先发现题目就是在模拟冒泡排序,而交换的次数,就是冒泡排序的交换次数就是我们的逆序对个数,至于求逆序对最快的方法,就是归并排序。
还要注意这里一定要用Longlong,不然的话就会WA,因为最大值可能为$n*n$ 作者就是这个傻孩子
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N=501000;
#define ll long long
ll n,m,i,j,k,a[N],b[N],cnt;
void merge(ll a[],ll l,ll r)
{
if (r-l<1)
return ;
ll mid=(l+r)>>1;
merge(a,l,mid);
merge(a,mid+1,r);
ll i=l,j=mid+1;
for (ll k=l;k<=r;k++)
{
if (j>r || i<=mid && a[i]<=a[j])
b[k]=a[i++];
else
{
cnt+=mid-i+1;
b[k]=a[j++];
}
}
for (ll k=l;k<=r;k++)
a[k]=b[k];
}
int main()
{
ios::sync_with_stdio(false);
while(cin>>n && n)
{
for (i=1;i<=n;i++)
cin>>a[i];
cnt=0;
merge(a,1,n);
cout<<cnt<<endl;
}
return 0;
}
只通过比较和交换相邻两数数值的排序方法,显而易见就是冒泡排序。在排序过程中每找到一对大小颠倒的相邻数值,交换,就会使整个序列的逆序对少1个。最终排好序的序列逆序对个数显然为0,所以对a进行冒泡排序需要的最少交换次数就是序列a中逆序对的个数。我们直接用归并排序求出a的逆序对个数就是本题的答案。
听了大佬的指点茅塞顿开,🐂
if (j>r || i<=mid && a[i]<=a[j])什么时候j会大于r哩
大佬,究竟使用归并好还是冒泡呢??
至于求逆序对最快的方法,就是归并排序。
thanks,大佬
我不是大佬
不对吧???树状数组也是nlogn
但是没那么方便
为啥逆序对的数量与冒泡排序的交换次数相等呢?模拟了几组数据发现确实是这样,但为什么呢?
想一想冒泡排序中是不是只有逆序才会导致一次相邻的交换
只通过比较和交换相邻两数数值的排序方法,显而易见就是冒泡排序。在排序过程中每找到一对大小颠倒的相邻数值,交换,就会使整个序列的逆序对少1个。最终排好序的序列逆序对个数显然为0,所以对a进行冒泡排序需要的最少交换次数就是序列a中逆序对的个数。我们直接用归并排序求出a的逆序对个数就是本题的答案。
问一下cnt+=mid-i+1;这一句该怎么理解?把j位上的数放到k位上,不就是要和kj有关啥的吗?
啥,[mid,i]这个区间的每一个数都可以和当前数构成逆序对。。。
哦哦懂了,我原来以为在原序列中位置是j,新序列位置是k,求要交换的次数,得到的是逆序对,多谢
是滴。
嗷嗷,真棒!我开始也没明白!一说就懂了
ios::sync_with_stdio(false); 这个是什么意思呢 大佬。
您可以认为是一个固定的语法。
加上这句话后,$scanf,printf$不可以使用
但是cin,cout的读入速度直线飙升。
建议搭配$上cin.tie(0),cout.tie(0)$
这样读入速度和$scanf$基本上一样了
噢噢 ,长知识了 谢谢大佬
不谢啦~
用了long long,但我又把输出格式写成%d。。。
注意下,下回别犯就好了.= ̄ω ̄=
真实
一直用%d写完代码发现#define int long long…
呵呵呵……无奈╮(╯▽╰)╭
挺无语的