acm逆序数

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。

如2 4 3 1中,2 1,4 3,4 1,3 1是逆序,逆序数是4。给出一个整数序列,求该序列的逆序数。

Input

第1行:N,N为序列的长度(n <= 50000) 
第2 - N + 1行:序列中的元素(0 <= Aii <= 10^9)

Output

输出逆序数

Sample Input

4
2
4
3
1

Sample Output

4

分治的典型题目,可怜的小白以为这题十分简单,毫不犹豫就贴上了自己的代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
ll a,b,c,d;
ll r[50010];
int main(){
    memset(r,0,sizeof(r));
    scanf("%lld",&a);
    for(b=0;b<a;b++){
        scanf("%lld",&r[b]);
    }
    c=0;
    for(ll i=0;i<a;i++){
        for(ll j=i+1;j<a;j++){
            if(r[i]>r[j]){
                c++;
            }
        }
    }
    printf("%lld\n",c);
    return 0;
}

结果可想而知TLE啦,这道题实在是坑,时间很少,但是数据达到50000,做题不要慌啊。。题解如下:

采用分治递归的方法,首先既然采用递归就要有一个递归结束的点,如果以中间数为基数mid,那么当基数与两侧的数相同时也就是递归结束的点,然后再回溯。但是如果仅仅是回溯的话,不要慌,请看代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
ll a,b,c,d,x,y;
ll r[50010];
ll solve(ll i,ll j){
    if(i==j){
        return 0;
    }
    ll num=0;
    ll mid=(i+j)/2;
    num+=solve(i,mid);
    num+=solve(mid+1,j);
    for(x=i,y=mid+1;x<=mid;){
        if(y>j){
            y=mid+1;
            x++;
            if(x>mid){
                return num;
            }
        }
        if(r[x]>r[y]){
            num++;
        }
        y++;
    }
}
int main(){
    memset(r,0,sizeof(r));
    scanf("%lld",&a);
    for(b=0;b<a;b++){
        scanf("%lld",&r[b]);
    }
    c=solve(0,a-1);
    printf("%lld\n",c);
}

结果还是TLE,我就纳闷了,难道还有比这更快的算法吗?经过大佬的指点,我算是彻底醒悟了,我们可以定义一个数组,来对已经比较过得区间排序,这个排序的意思是已经比较过得它不影响接下来的比较的,相反还会减小函数的复杂度。不好想呀,小白的我也是看了好久的啊,行了话不多说,上代码:

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
ll a,b,c,d,x,y,z,m;
ll r[500100],r1[500100];//r记录总的数据,r1用来保存比较过的数组
ll solve(ll i,ll j){
    if(i==j){
        return 0;//递归结束的点,回溯的起点
    }
    ll num=0;
    ll mid=(i+j)/2;//把区间分割成一个一个的子段
    num+=solve(i,mid);
    num+=solve(mid+1,j);//对每个子段分治递归
    //用r1来记录此区间内的排序,然后对应于r的子段进行重新赋值
    for(x=i,y=mid+1,z=0;x<=mid||y<=j;z++){//彻底明白每一个变量的含义
        if(y>j){
            r1[z]=r[x];
            x++;
        }
        else if(x>mid){
            r1[z]=r[y];
            y++;
        }
        else if(r[x]<=r[y]){
            r1[z]=r[x];
            x++;
        }
        else{
            r1[z]=r[y];
            y++;
            num+=mid-x+1;
        }
    }
    for(m=0;m<=j-i;m++){
        r[i+m]=r1[m];
    }
    return num;
}
int main(){
    scanf("%lld",&a);
    memset(r,0,sizeof(r));//看心情清空
    for(b=0;b<a;b++){
        scanf("%lld",&r[b]);
    }
    c=solve(0,a-1);
    printf("%lld\n",c);
    return 0;
}

加油!

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旺旺的碎冰冰~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值