51 nod 1019 逆序数

1019 逆序数

基准时间限制:1 秒                        空间限制:131072 KB                  分值: 0                    难度:基础题

Problem Description

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

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

Input

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

Output

输出逆序数

Input示例

4
2
4
3
1

Output示例

4

 

题意很明显,就不多说了,思路就是分治归并  O ( n * log n)

其实如果时间不限制的话,最容易想到的一定是暴力for循环直接求解   O(n ^ 2),(我一上来其实有这个想法,但是考虑到时间,所以我用了二分区间,以为这样就可以水过,,,,,结果很明显  Time Limite Exceed  ,),其实这一题解法很多,但是个人感觉分治归并好一点。。。。

 

自我感觉到发布博客的现在,也不算完全理解数列分治归并这种方法。。。。。。。说到这就简单的说一下这个方法:

 

分治与归并:

  1. 数列上的分治
  2. 平面上的分治
  3. 树上的分治

1. 数列上的分治:(求逆序数)

逆序数:其实就是快速排序的次数

百度百科:

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数。一个排列中所有逆序总数叫做这个排列的逆序数。也就是说,对于n个不同的元素,先规定各元素之间有一个标准次序(例如n个 不同的自然数,可规定从小到大为标准次序),于是在这n个元素的任一排列中,当某两个元素的先后次序与标准次序不同时,就说有1个逆序。一个排列中所有逆序总数叫做这个排列的逆序数。

2.平面上的分治:( 求最近点 )

例题:

给定平面上n个点,求距离最近的两个点的距离;1 <= n <= 1e4

解题思路:


我们考虑将平面的点按x坐标排序,然后将平面一分为二,那么最短距离只存在两种情况在划分的平面内和在划分线两侧(即在不同平面内)的最短距离,对此我们可以先求出在划分的左右平面内的最短距离,比较一下,设较小的那个距离为d,然后设划分线的x坐标为x0,那么我们要找的距离比d小的点对的x坐标一定在(x0-d,x0+d)内,而y坐标的差值一定小于d,这样考虑的范围就很小了(对于一个点来说,最多比较6个点),我们在考虑的范围内比较y坐标,将满足条件的统计下来即可;

 

情况1:                                                                       

  情况2:

3.树上的分治:

例题:

例如给你一颗有n个顶点构成的树,其中连接顶点a和b的长度为l,问最短距离不超过k的顶点的对数.

解题思路:

考虑将树按重心s划分,假设分成了三颗子树A,B,C;那么最短距离不超过k的顶点对只可能存在三种情况两个顶点存在于同一颗子树,以重心s为一个顶点,另一个在子树内的顶点对,还有两颗顶点存在于不同子树的顶点对;对于第一种情况,我们可以递归得到,第二种情况可以直接计算出所有子树里的顶点到s的距离,统计即可,第三种情况我们添加一个到s距离为0的顶点就可以转化为第二种情况啦。注意我们第二种统计的顶点对包含了第一种情况统计的顶点对数,所以要减去第一种情况以避免重复统计

树的重心
定义:树的重心也叫树的质心。找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删
去重心后,生成的多棵树尽可能平衡。由定义可知,每颗子树的节点个数不超过n/2;所以每次以树的重心为分割点,那么每次树的大小至少减半,所以递归深度为O(logn);

示意图:(i 节点为该图树的重心)

   

 

 

AC代码:

#include <stdio.h>//scanf printf
#include <algorithm>//max min sort
#include <iostream>//cin cout
#include <string.h>//strcmp memset
#include <string>//str
#include <queue>//que
#include <stack>//sta
#include <vector>//ve
#include <map>//map<string, int>
#include <set>
using namespace std ;
typedef long long ll;
#define MAXN 100000+10
int a[MAXN],ans[MAXN];

ll solve(int l, int r)
{
    int mid = (l + r) >> 1;//每次对区间进行二分
//递归结束的条件
    if(l == r)
    {
        return 0;
    }
    ll num = 0; //记录逆序对的个数
    num += solve(l, mid);//递归 一直二分区间直到区间只剩一个值
    num += solve(mid + 1, r);//递归 一直二分区间直到区间只剩一个值
//判断 num 跟新的条件,跟新 num   (注意 i 的起始值为 l ,不是一)
    for(int i = l, j = mid + 1, k = 0; i <= mid || j <= r; k ++)
    {
        if(i > mid) ans[k] = a[j++];
        else if(j > r)  ans[k] = a[i++];
        else if(a[i] <= a[j])   ans[k] = a[i++];
        else
        {
            ans[k] = a[j++];
            num += (mid - i + 1);
        }
    }
//对 a[] 数列的 (l-r) 项进行升序重新排序
    for(int i = 0; i <= (r - l); i ++)
        a[l + i] = ans[i];  //注意循环条件和排序的对象
    return num;
}

int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; i ++)
        scanf("%d", &a[i]);
    printf("%lld\n", solve(0, n-1));
    return 0;
}

 

 

 

另附有详细过程的学长代码:

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5+5;

int a[N], ans[N];

ll solve(int l, int r)
{
	cout << "l = " << l << " " << "r = " << r <<endl;
    //划分并解决子问题
    int mid = (l+r)>>1;
    cout << "mid = " << mid << endl;
    if(l==r)
	{
		cout << "return" << endl;
		return 0;
	}
    ll num = 0;//逆序对的个数

    num += solve(l, mid);//递归 依次二分区间 直到区间中只包含一个元素
    num += solve(mid+1, r);

    //合并子问题

    //每一次的处理结果,升序保存在ans数组
    //将属于不同子序列的逆序对个数累加
    for(int i = l, j = mid+1, k = 0; i<=mid||j<=r; k ++)
    {
        if(i>mid)   ans[k] = a[j++];
        else if(j>r)    ans[k] = a[i++];
        else if(a[i]<=a[j]) ans[k] = a[i++];
        else
        {
            //出现逆序对
            ans[k] = a[j++];
            num += mid-i+1;//B序列中大于a[j]的个数
        }
    }
    for(int i = 0; i <= (r-l); i ++)
        a[l+i] = ans[i];
    for(int i = 0; i <= r; i++)
        printf("%d ", a[i]);
    puts("");//用来换行
    cout << "num = " << num << " " << "return" << endl;
    return num;
}

int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; i ++)
        scanf("%d", a+i);
    printf("%lld\n",solve(0, n-1));
    return 0;
}

 

 

 

 

鸡汤一碗,先干为敬!!!!

        当你是地平线上一棵草的时候,不要指望别人会在远处看到你,即使他们从你身边走过甚至从你身上踩过,也没有办法,因为你只是一棵草;而如果你变成了一棵树,即使在很远的地方,别人也会看到你,并且欣赏你,因为你是一棵树!

                                                                                                                                                                                ------------俞敏洪

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值