原题传送门:逆序对 - 洛谷
一道蒟蒻见了就流泪的题,按照题目规则走了一次,实打实的0分。
TLE代码(0分)
#include <bits/stdc++.h>
using namespace std;
const int MAXN=5e5+1;
int n,a[MAXN];
set<pair<int,int>>s;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(a[i]>a[j])
s.insert({a[i],a[j]});
}
}
cout<<s.size();
return 0;
}
但是仔细研究了一下之后发现(看了题解之后发现),这似乎就是一道排序问题。
例如:2,4,3。只要前面大于后面那么就是逆序对,猛然发现如果按照升序排序,那么只有在前面大于后面的时候才会发生交换,也就是冒泡排序。于是这道题就做出来了
TLE代码2(25分)
#include <bits/stdc++.h>
using namespace std;
const int MAXN=5e5+1;
int n,a[MAXN],ans;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
//冒泡排序 前面要大于后面的 所以小的往前冒
for(int i=n;i>=2;i--){
for(int j=n;j>=2;j--){
if(a[j-1]>a[j]){
swap(a[j],a[j-1]);
ans++;
}
}
}
cout<<ans;
return 0;
}
n^2冒泡都不给过!?,冒泡不行的话就会想到更快更稳定的归并排序,为什么不用快速排序?因为不稳定,而且我也不知道源代码是什么。
这道题数列排序 - 洛谷和逆序对基本上一样,不知道为什么难度不一样。
前置知识点:归并排序
核心:两个有序序列通过贪心,比较大小之后插入新数组,依然是有序序列。
设两个有序序列插入完成之后的序列c{a1,a2,a3,...,an-1,an}
两个有序序列,a{ai,aj,ak},b{al,am,an},其中i,j,k,l,m,n都在[1,n]范围内
如果ai>al,那么原序列中ai肯定也在al后面,所以把al放在最前面,同时ai=min(a),al=min(b)
那么必然有al=min(c),al放好之后,弹出al,即b{am,an},继续执行这一步即可证明其正确性
以这个线段树的左半边为例 ,如果是叶子节点那么我们可以认为它已经是有序的,所以不用考虑,如果不是,就从小到大依次有序放入辅助数组,再让辅助数组给原数组赋值,就可以实现递归操作。
当然要放在递归的后序遍历上,即左-右-根。左儿子有序了,右儿子有序了,那么根也可以有序了。上图的[1,2]和3,[1,2]内部是1和2分别都是有序的(单个就认为是有序),所以[1,2]有序,3是单个的,那么[1,3]也有序了。
归并排序代码
#include <bits/stdc++.h>
using namespace std;
int n=10,b[10];
int a[10]={3,2,1,3,4,7,4,1,63124,53612};
void merge_sort(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
merge_sort(l,mid);
merge_sort(mid+1,r);
int i=l,j=mid+1,k=i;
while(i<=mid&&j<=r){//左右序列合并
if(a[i]<a[j])b[k++]=a[i++];
else b[k++]=a[j++];
}
while(i<=mid)b[k++]=a[i++];//可能右序列合完了左序列还没用完
while(j<=r)b[k++]=a[j++];//同理
for(i=l;i<=r;i++)a[i]=b[i];//赋值回去,完成合并
}
void printArray(){
for(int i=0;i<10;i++)
cout<<a[i]<<" ";
cout<<endl;
}
int main(){
cout<<"原序列:";printArray();
merge_sort(0,n-1);
cout<<"归并排序(升序):";printArray();
return 0;
}
至此归并排序已经全部学完了,那么接着来逆序对。看个毛直接AC了
AC代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=5e5+1;
int n,a[MAXN],b[MAXN];
long long ans;
/* [5,6,7][1,2,9]
* i m j->
* 因为两边都必然是有序的
* 当判断一次j的时候(即j->右移时)
* 必然有逆序对m-i+1个,因为i右边的肯定比左边大
* 如果不一起统计那么j还要回溯再和i+1和i+2去判断
* */
void merge_sort(int l,int r){//升序,左区间有大于右区间的,就是产生了逆序对
if(l==r)return;
int mid=(l+r)>>1;
merge_sort(l,mid);
merge_sort(mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r){
if(a[i]<=a[j])b[k++]=a[i++];
else b[k++]=a[j++],ans+=mid-i+1;
}
while(i<=mid)b[k++]=a[i++];
while(j<=r)b[k++]=a[j++];
for(i=l;i<=r;i++)a[i]=b[i];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
merge_sort(1,n);
printf("%lld",ans);
return 0;
}
[NOIP2013 提高组] 火柴排队 - 洛谷,一道稍微魔改了一下的逆序对。
有几个比较难的点
1.化简题目中给的求和公式,可以得出目标要ai*bi求和最大.
若ai*bi求和要最大,则a序列和b序列都必须各自有序.(有序相乘>=乱序相乘)
蒟蒻在此不做证明(即,在各自序列中分别都是第i小的ai*bi才是最大值)
2.对输入好的a序列和b序列不排序进行编号.
然后根据高度排序,显然编号是根据高度排序的排序(根据原高度升序后变化).
样例分析.{序列值}[序列编号],编号映射f[i]=j(代表a序列的编号对应b序列的编号)
| a{3,2,1,4}[1,2,3,4]->[3,2,1,4]
| b{2,3,9,5}[1,2,3,4]->[1,2,4,3]
|
| 原映射f[1]=1,f[2]=2,f[3]=3,f[4]=4
|目标映射f[3]=1,f[2]=2,f[1]=4,f[4]=3
不难发现目标映射是我们想要的理想情况.
也就是说只要求出 交换任意两个映射的值 使得原映射变成目标映射 的最少交换次数 即可.
但是从有序到无序是否有点太累了?
执行排序的逆操作,使得无序变成有序照样可以得到正确答案.
因此问题转换为逆序对问题(排序问题).原因如下:
每次只能交换相邻两根火柴
[3,2,1],3要回到自己第三大的位置,势必要让比他小的两个数都去他的左边
即逆序对的数量
可以假设任意n个整数去证明.