[NOIP2013]火柴排队

                                  【NOIP2013】火柴排队

时间限制: 1 Sec  内存限制: 128 MB

题目描述

涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。现在将每盒中的火柴各自排成一列,同一列火柴的高度互不相同,两列火柴之间的距离定义为:,其中 ai表示第一列火柴中第 i 个火柴的高度,bi表示第二列火柴中第 i 个火柴的高度。

每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对 99,999,997取模的结果。

输入

共三行,第一行包含一个整数n,表示每盒中火柴的数目。

第二行有n个整数,每两个整数之间用一个空格隔开,表示第一列火柴的高度。

第三行有n个整数,每两个整数之间用一个空格隔开,表示第二列火柴的高度

输出

输出共一行,包含一个整数,表示最少交换次数对 99,999,997 取模的结果。

样例输入

4
2 3 1 4
3 2 1 4

样例输出

1

提示

【输入输出说明】


最小距离是 0,最少需要交换 1 次,比如:交换第 1 列的前 2 根火柴或者交换第 2 列的前 2 根火柴。


对于 10%的数据, 1 ≤ n ≤ 10;对于 30%的数据,1 ≤ n ≤ 100;对于 60%的数据,1 ≤ n ≤ 1,000;


对于 100%的数据,1 ≤ n ≤ 100,000,0 ≤火柴高度≤ 231 − 1。

 

 

方法一:(归并排序)
对于这道题,首先需要解决的问题是如何找到最小的解,这个问题还是比较好想,就是让两个队列分别排序,然后一一对应算出的解就是正解,这个东西好像可以用排序不等式证明。(真正的证明应该有些困难,但我们都能感觉得到正解就是如此)
需要解决的第二个问题就是意识到寻找交换的最小次数就是找逆序对的数量。这个问题首先需要排序两个队列之前将队列的元素在原排列中的位置记录下来,然后假设第一个队列的元素不动,第二个队列交换元素的位置直至达到正解,本质就是寻找逆序对(元素的原排列),逆序对可以用归并排序(时间复杂度为O(nlogn))。
要实现上面的逆序对必须有一个操作,就是开一个数组来存第一个队列的原排列的位置上应该放置哪一个第二队列里的元素的原排列位置。

 

下面是我的代码

 

 

#include
#include
#define MAXN 100000
using namespace std;
struct node
{
	int x,cnt;
}A[MAXN+10],B[MAXN+10];
int aa[MAXN+10];
int T[MAXN+10];
int ans=0;
bool cmp(node a,node b)
{
	return a.xr)||(aa[i]<=aa[j])))  
            T[k]=aa[i++];  
        else{ 
            T[k]=aa[j++]; 
            ans+=mid-i+1; 
			ans%=99999997;
        }    
    for(i=l;i<=r;i++)  
        aa[i]=T[i];  
}  
void margef(int l,int r)  
{  
    if(l

 

 

 

方法二:树状数组求逆序对
下面是我转载的树状数组求逆序对的解释:

转载:

树状数组,具体的说是 离散化+树状数组。这也是学习树状数组的第一题.

算法的大体流程就是:

1.先对输入的数组离散化,使得各个元素比较接近,而不是离散的,

2.接着,运用树状数组的标准操作来累计数组的逆序数。

算法详细解释:

1.解释为什么要有离散的这么一个过程?

    刚开始以为999.999.999这么一个数字,对于int存储类型来说是足够了。

    还有只有500000个数字,何必要离散化呢?

    刚开始一直想不通,后来明白了,后面在运用树状数组操作的时候,

    用到的树状数组C[i]是建立在一个有点像位存储的数组的基础之上的,

    不是单纯的建立在输入数组之上。

    比如输入一个9 1 0 5 4,那么C[i]树状数组的建立是在,

    下标 0 1 2 3 4 5 6 7 8 9

    数组 1 1 0 0 1 1 0 0 0 1

    现在由于999999999这个数字相对于500000这个数字来说是很大的,

    所以如果用数组位存储的话,那么需要999999999的空间来存储输入的数据。

    这样是很浪费空间的,题目也是不允许的,所以这里想通过离散化操作,

    使得离散化的结果可以更加的密集。

2. 怎么对这个输入的数组进行离散操作?

   离散化是一种常用的技巧,有时数据范围太大,可以用来放缩到我们能处理的范围;

   因为其中需排序的数的范围0---999 999 999;显然数组不肯能这么大;

   而N的最大范围是500 000;故给出的数一定可以与1.。。。N建立一个一一映射;

   ①当然用map可以建立,效率可能低点;

   ②这里用一个结构体

   struct Node

   {

      int v,ord;

   }p[510000];和一个数组a[510000];

   其中v就是原输入的值,ord是下标;然后对结构体按v从小到大排序;

   此时,v和结构体的下标就是一个一一对应关系,而且满足原来的大小关系;

   for(i=1;i<=N;i++) a[p[i].ord]=i;

   然后a数组就存储了原来所有的大小信息;

   比如 9 1 0 5 4 ------- 离散后aa数组就是 5 2 1 4 3;

   具体的过程可以自己用笔写写就好了。

3. 离散之后,怎么使用离散后的结果数组来进行树状数组操作,计算出逆序数?

    如果数据不是很大, 可以一个个插入到树状数组中,

    每插入一个数, 统计比他小的数的个数,

    对应的逆序为 i- getsum( aa[i] ),

    其中 i 为当前已经插入的数的个数,

    getsum( aa[i] )为比 aa[i] 小的数的个数,

    i- sum( aa[i] ) 即比 aa[i] 大的个数, 即逆序的个数

    但如果数据比较大,就必须采用离散化方法

    假设输入的数组是9 1 0 5 4, 离散后的结果aa[] = {5,2,1,4,3};

在离散结果中间结果的基础上,那么其计算逆序数的过程是这么一个过程。

1,输入5,   调用upDate(5, 1),把第5位设置为1

1 2 3 4 5

0 0 0 0 1

计算1-5上比5小的数字存在么? 这里用到了树状数组的getSum(5) = 1操作,

现在用输入的下标1 - getSum(5) = 0 就可以得到对于5的逆序数为0。

2. 输入2, 调用upDate(2, 1),把第2位设置为1

1 2 3 4 5

0 1 0 0 1

计算1-2上比2小的数字存在么? 这里用到了树状数组的getSum(2) = 1操作,

现在用输入的下标2 - getSum(2) = 1 就可以得到对于2的逆序数为1。

3. 输入1, 调用upDate(1, 1),把第1位设置为1

1 2 3 4 5

1 1 0 0 1

计算1-1上比1小的数字存在么? 这里用到了树状数组的getSum(1) = 1操作,

现在用输入的下标 3 - getSum(1) = 2 就可以得到对于1的逆序数为2。

4. 输入4, 调用upDate(4, 1),把第5位设置为1

1 2 3 4 5

1 1 0 1 1

计算1-4上比4小的数字存在么? 这里用到了树状数组的getSum(4) = 3操作,

现在用输入的下标4 - getSum(4) = 1 就可以得到对于4的逆序数为1。

5. 输入3, 调用upDate(3, 1),把第3位设置为1

1 2 3 4 5

1 1 1 1 1

计算1-3上比3小的数字存在么? 这里用到了树状数组的getSum(3) = 3操作,

现在用输入的下标5 - getSum(3) = 2 就可以得到对于3的逆序数为2。

6. 0+1+2+1+2 = 6 这就是最后的逆序数

分析一下时间复杂度,首先用到快速排序,时间复杂度为O(NlogN),

后面是循环插入每一个数字,每次插入一个数字,分别调用一次upData()和getSum()

外循环N, upData()和getSum()时间O(logN) => 时间复杂度还是O(NlogN).

最后总的还是O(NlogN).

 

然后是实现的代码
这个代码没有使用上述的方法,改进了一下,上述方法是正序添加元素寻找比它小的值,还需要用添加的总元素减去比它小的元素就是比它大的元素。下面的代码只需要逆序添加元素,在已添加的元素中寻找比它小的元素,就不需要用添加的总元素再去减了。
#include
#include
#include
 using namespace std;
 #define MAXN 100010
#define MOD 99999997
struct node{
    int v,t;
}a[MAXN],b[MAXN];
bool cmp(node x,node y)
{
    return x.v=1;i--)
	{
		ans+=Sum(aa[i]);
		Add(aa[i]);
		ans%=MOD;
	}
    printf("%d\n",ans);
    return 0;
}


 

 

你可能感兴趣的:(NOIP2013)