洛谷 P1908 逆序对

原题传送门:逆序对 - 洛谷

cecd9dcf62724aa0bc7e7645c263c482.png

550531b4b2d643efb36fd90c45ed445c.png


 一道蒟蒻见了就流泪的题,按照题目规则走了一次,实打实的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冒泡都不给过!?,冒泡不行的话就会想到更快更稳定的归并排序,为什么不用快速排序?因为不稳定,而且我也不知道源代码是什么

这道题数列排序 - 洛谷和逆序对基本上一样,不知道为什么难度不一样。

前置知识点:归并排序

384c8c23f36640268902494ca4d2b744.png

核心:两个有序序列通过贪心,比较大小之后插入新数组,依然是有序序列。

设两个有序序列插入完成之后的序列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;
}

82f5490b1b6b4ab4b2ad7dc899c68c60.png

 至此归并排序已经全部学完了,那么接着来逆序对。看个毛直接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个整数去证明.
  

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值