NOIP 2013 火柴排队 题解(离散化+树状数组)

题目描述

涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。现在将每盒中的火柴各自排成一列,同一列火柴的高度互不相同,两列火柴之间的距离定义为:这里写图片描述
,其中 ai表示第一列火柴中第 i 个火柴的高度,bi表示第二列火柴中第 i 个火柴的高度。
每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对 99,999,997 取模的结果。

输入

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

输出

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

样例输入

[Sample 1]
4
2 3 1 4
3 2 1 4
[Sample 2]
4
1 3 4 2
1 7 2 4

样例输出

[Sample 1]
1
[Sample 2]
2

提示

【样例1说明】

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

【样例2说明】

最小距离是 10,最少需要交换 2 次,比如:交换第 1 列的中间 2 根火柴的位置,再交换第 2 列中后 2 根火柴的位置。

【数据范围】

对于 10%的数据, 1 ≤ n ≤ 10;

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

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

对于 100%的数据,1 ≤ n ≤ 100,000,0 ≤火柴高度≤ 2^31 - 1。

题解:

要使得值最小,应当使a盒火柴的第K大与b盒火柴的第K大放在一起。

//数学证明:令a>b,c>d,判断(a-c)^2+(b-d)^2-[(a-d)^2+(b-c)^2]的正负。

因此将两盒火柴都快排一遍可知对应关系;

如果开一个node{ int lenth,ord}来储存高度和原来的位置,那么快排后可知原序列中第几根火柴应该和第几根火柴放在一起。

这时开一个数组 a [ ]来记录对应关系,例如a[i]=j表示第一组火柴的第i根对应第二组火柴第j根。

此时,a[ ]中有多少对逆序数对,就要交换几次。

请看一组样例解释:

先给出火柴高度,[ ]里是在原队列的位置:

8 [1] 6 [2] 9 [3] 2 [4] 4 [5] 5 [6]
1 [1] 5 [2] 2 [3] 8 [4] 6 [5] 3 [6]

将两组火柴排序后得到如下所示(令这里写图片描述 最小的情况):
2 [4]   4 [5]   5 [6]   6 [2]   8 [1]   9 [3]
1 [1]   2 [3]   3 [6]   5 [2]   6 [5]   8 [4]
开辟a[ ]来储存原位置对应关系:
a[4]==1  a[5]==3  a[6]==6  a[2]==2  a[1]==5  a[3]==4

整理:
a[1]==5 a[2]==2 a[3]==4 a[4]==1 a[5]==3 a[6]==6

那么为什么所求答案就成了a[ ]的逆序对数目?

原因:

首先答案的情况:
2 [4] 4 [5] 5 [6] 6 [2] 8 [1] 9 [3]
1 [1] 2 [3] 3 [6] 5 [2] 6 [5] 8 [4]
若将第一组火柴按原位置排好,并且令第二组火柴跟着第一组中对应的火柴排好得到:
8 [1] 6 [2] 9 [3] 2 [4] 4 [5] 5 [6]
6 [5] 5 [2] 8 [4] 1 [1] 2 [3] 3 [6]
由此可得 a[1]==5 a[2]==2 a[3]==4 a[4]==1 a[5]==3 a[6]==6
如果要使**这里写图片描述**最小,应该让a[i]==i;
所以要交换火柴序号(在现实中通过交换火柴实现)即a[i]的值使a[i]==i;
交换次数即答案,也是a[ ]的逆序队数目。

求逆序数对可以用树状数组,这里不详讲,也可以用其它方法实现。

见代码:

树状数组版:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
struct node{
    int num,ord;
};
node team1[100010],team2[100010];
int n,a[100010],tree[100010];
int lowbit(int k){
    return k&-k;
}    //树状数组操作
int scan(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')
     x=x*10+c-'0',
     c=getchar();
    return x;
}   //读入优化
void update(int k,int numb){
    int i;
    for(i=k;i<=n;i+=lowbit(i))
     tree[i]+=numb;
    return ;
}  //树状数组操作
bool comp(node x,node y){
    return x.num<=y.num;
}

int get_sum(int k){
    int sum=0;
    for(int i=k;i>=1;i-=lowbit(i))
     sum+=tree[i];
    return sum;
}
int main(){
    int i,answer=0;
    n=scan();
    for(i=1;i<=n;i++)
     team1[i].num=scan(),
     team1[i].ord=i;  //位置记录
    for(i=1;i<=n;i++)
     team2[i].num=scan(),
     team2[i].ord=i;
    sort(team1+1,team1+1+n,comp);//按高度排序
    sort(team2+1,team2+1+n,comp);
    for(i=1;i<=n;i++)
     a[team1[i].ord]=team2[i].ord; //离散化
    for(i=1;i<=n;i++){
        update(a[i],1);
        answer=(answer+i-get_sum(a[i]))%99999997 ;
    }     //树状数组求逆序对数目
    printf("%d",answer%99999997 );
}

完毕!!!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值