Luogu 1372 数列排序
上午学校搞模拟赛的第一题——其实真的是够了……快排+二分能过,不过题解给的快排+数学方法看起来也不错,这里只说说我的做法:
题目描述
给定一个数列{an},这个数列满足ai≠aj(i≠j),现在要求你把这个数列从小到大排序,每次允许你交换其中任意一对数,请问最少需要几次交换?
输入输出格式
输入格式:
第一行,正整数n (n<=100,000)。
以下若干行,一共n个数,用空格分隔开,表示数列{an},任意-231<ai<231。
输出格式:
只有一行,包含一个数,表示最少的交换次数。
输入输出样例
输入样例#1:
8
8 23 4 16 77 -5 53 100
输出样例#1:
5
首先看题第一反应就是选择排序——为啥呢?除了桶排这个不需要交换的排序算法以外,选择排序是执行交换次数最少的排序,注意这里是交换顺序不是时间复杂度……
紧接着——有一个定理——用选择排序排序时交换次数就是最少的,也就是我们的输出,然而我觉着这个并不能证明……那么问题来了,选择排序时间复杂度为O(n^2)很明显100000的数据是妥妥的炸了,于是选择排序废了
继续下一个思路——还记得树状数组逆序对么?或者说还记得归并排序逆序对么?然而经过试验——或者是手动笔算,你会发现逆序对根本就是WA——也就是说最小交换次数又不是逆序对个数
没办法我们只好来自行模拟选择排序的过程……其实只是大概借鉴了一下选排的思路——首先我们Sort一遍把序列排好,当然要保存原来的序列,这里我用了r(其实是一开始用归并排序剩下来的r数组)
然后呢?按照选排的方法卡就行了——首先我们找到r[1],然后在排好序的a中找到对应的元素——假设是第k个元素——把r[1]与r[k]交换过来,cnt++——接着继续找r[2]……这时可以轻易地发现——a中元素单调递增,满足二分查找的性质,于是我们来写个二分查找解决查找k的步骤——总时间复杂度O(n log n)
然而通过模拟样例可以发现一个漏洞——就是我们只搜一遍还不行,因为r数组有可能根本没排序完成,于是我们来第二遍、第三遍,反正只要r排好序了就行,相当于我们完成了选排的任务,这时候写个While来判断r是否有序就好啦,别忘了判断是否是第一个first,具体看代码自己看一下就好啦。
下面贴上AC代码——
#include <cstdio>
#include <algorithm>
using namespace std;
int a[300000],r[300000],n,t,i,cnt,k;
bool b=false,first;
int find(int num)
{
int l=1,r=n,mid;
while (l!=r)
{
mid=(l+r)>>1;
if (a[mid]>=num) r=mid;
else l=mid+1;
}
return l;
}
int main()
{
freopen("seqsort.in","r",stdin);
freopen("seqsort.out","w",stdout);
scanf("%d",&n);
for (i=1;i<=n;i++)
{
scanf("%d",&a[i]);
r[i]=a[i];
}
sort(a+1,a+n+1);
while (!b)
{
first=true;
for (i=1;i<=n;i++)
{
k=find(r[i]);
if (k!=i)
{
b=false;
t=r[i]; r[i]=r[k]; r[k]=t;
cnt++;
}
else
if (first) { b=true; first=false; }
else b=b&&true;
}
}
printf("%d",cnt);
fclose(stdin); fclose(stdout);
return 0;
}