NOIp 2013 提高组 火柴排队 题解

【问题描述】
涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。现在将每盒中的火柴各自 排成一列,同一列火柴的高度互不相同,两列火柴之间的距离定义为:

i=1n(aibi)2

其中 ai 表示第一列火柴中第 i 个火柴的高度,bi 表示第二列火柴中第 i 个火柴的高度。 每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对 99,999,997 取模的结果。
【输入】
输入文件为 match.in。 共三行,第一行包含一个整数 n,表示每盒中火柴的数目。 第二行有 n 个整数,每两个整数之间用一个空格隔开,表示第一列火柴的高度。 第三行有 n 个整数,每两个整数之间用一个空格隔开,表示第二列火柴的高度。
【输出】
输出文件为 match.out。 输出共一行,包含一个整数,表示最少交换次数对 99,999,997 取模的结果。
【输入输出样例 1】

match.inmatch.out
42
2 3 1 4
3 2 1 4

【输入输出样例说明】
最小距离是 0,最少需要交换 1 次,比如:交换第 1 列的前 2 根火柴或者交换第 2 列的前 2 根火柴。
【输入输出样例 2】

match.inmatch.out
42
1 3 4 2
1 7 2 4

【输入输出样例说明】
最小距离是 10,最少需要交换 2 次,比如:交换第 1 列的中间 2 根火柴的位置,再交换第 2 列中后 2 根火柴的位置。
【数据范围】
对于 10%的数据, 1 ≤ n ≤ 10;
对于 30%的数据,1 ≤ n ≤ 100;
对于 100%的数据,1 ≤ n ≤ 100,000,0 ≤火柴高度≤ 2^32 − 1。

【解题思路】
首先如果要解决这道题,就需要先了解一下什么叫排序不等式。排序不等式完整说明如下:
设数列A={a1,a2,a3,…,an},B={b1,b2,b3,…,bm}且满足a1≤a2≤a3≤…≤an,b1≤b2≤b3≤…≤bm。如果C={c1,c2,c3,…,cm}且C为B中元素的任意一个排列那么有如下概念:
将a1b1+a2b2+a3b3+…+anbm称为顺序积之和,简称顺序和;
将a1bm+a2b m-1 +a3b m-2+…+anb1称为逆序积之和,简称逆序和;
将a1c1+a2c2+a3c3+…+ancn称为乱序积之和,简称乱序和。
那么有顺序和>乱序和>逆序和。①

了解了什么叫排序不等式,那么这题看起来就简单多了。首先把距离公式变一下形,得到:

(a1b1)2+(a2b2)2+(a3b3)2+...+(anbn)2

去括号得到:
(a12+a22+a32+...+an2+b12+b22+b32+...+bn2)2(a1b1+a2b2...+anbn)

由于所有火柴的高度是给定的,所以
a12+a22+a32+...+an2+b12+b22+b32+...+bn2

是确定的。
那么我们只需要
a1b1+a2b2+...+anbn
最大即可。
由以上给出的排序不等式可知,当a1<a2<a3<…<an,b1<b2<b3<…<bn时,整体距离最小。
所以我们可以先将火柴高度数组a和b进行排序,再将a中最高与b中最高的火柴序号,a中次高与b中次高的火柴序号,…,a中最矮与b中最矮的火柴序号分别进行映射。
对于样例1,我们建立的映射就如这样所示:
1 2 3 4
2 1 3 4
对于样例2,建立的映射就是这样:
1 2 3 4
1 4 2 3
而进行一次交换就可以消除掉一对逆序对,那么问题的解即是求映射后下面那个数组的逆序对个数。至此,问题解决完毕。

可以写出如下程序:

#include <iostream>
#include <cstdio>
#include <algorithm>
#define MAXN 101101
#define MOD 99999997
using namespace std;
long long i,n,ans=0;
struct match{
    long long l;
    long long id;
}a[MAXN],b[MAXN];
int num[MAXN]={0},t[MAXN]={0};
int comp(match s,match k){
    return s.l<k.l;
}
void Merge(long long l,long long r){
    long long mid=(l+r)/2,i=l,j=mid+1,k=l;
    if(l>=r)return;
    Merge(l,mid);Merge(mid+1,r);
    while(i<=mid&&j<=r)
        if(num[i]>num[j]){t[k++]=num[j++];ans+=(mid-i+1);ans%=MOD;}
        else t[k++]=num[i++];
    while(i<=mid)t[k++]=num[i++];
    while(j<=r)t[k++]=num[j++];
    for(i=l;i<=r;i++)num[i]=t[i];
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n;
    for(i=1;i<=n;i++){cin>>a[i].l;a[i].id=i;}
    for(i=1;i<=n;i++){cin>>b[i].l;b[i].id=i;}
    sort(a+1,a+1+n,comp);
    sort(b+1,b+1+n,comp);
    for(i=1;i<=n;i++)num[a[i].id]=b[i].id;
    Merge(1,n);
    printf("%lld",ans);
    return 0;
}

①:严格来说,这个不等式是顺序和≥乱序和≥逆序和,且当a1=a2=a3=…=an或b1=b2=b3=…=bn时顺序和=逆序和。并且当C数列中元素排列顺序和B数列中一样时乱序和=顺序和。但是此题中每列中火柴高度都不相同,所以等号无法成立。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值