noip2013 火柴排队

题目描述

涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。 现在将每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 两列火柴之间的距离定义为: ∑(ai-bi)^2

其中 ai 表示第一列火柴中第 i 个火柴的高度,bi 表示第二列火柴中第 i 个火柴的高度。

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

输入输出格式

输入格式:

输入文件为 match.in。

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

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

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

输出格式:

输出文件为 match.out。

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

输入输出样例

输入样例#1:
【输入输出样例 1】
4
2 3 1 4
3 2 1 4
【输入输出样例 2】
4
1 3 4 2
1 7 2 4
输出样例#1:
【输入输出样例 1】
1
【输入输出样例 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 ≤火柴高度≤ maxlongint

思路:为了求最小的sigma,我们需要将每列的火柴高度有序化,此时问题就可以看成:要将火柴列A,B都转化成有序数列需要的交换次数。

那么,求有序转化的交换次数的问题,也可以看成求序列逆序对过程,而此题的难点就是需要我们求双序列逆序对。

首先解决单序列的逆序对问题,这里我考虑用我最熟悉的数据结构:树状数组(当然线段树也同样是解题利器,只不过改点求区间树状数组更简洁,优雅)。

算法过程1:利用逆序对定义「1」,数组A中,若i<j,而A[i]>A[j],则构成一对逆序对,我们可以这样设计:

                  确定树状数组代表的是一个数域,即数组A中元素值构成的一个区间,而维护区间中数存在的个数(想象桶排序是怎么做的?)。

                  从1到n遍历数组A,用A[i]的值更新数域。

                  因为当前树状数组中1~A[i]维护就是数组中下标小于i、且小于A[i]的数的个数,令这个数为T,所以已经纳入树状数组的数的个数i(已经更新了i个数,对吧?)-T=已经更新过却大于A[i]的数的个数S,利用「1」,得出S=当前数构成逆序对的个数。

                 输出S,算法完成。

其次,我们再解决双序列逆序对问题。

算法过程2:有什么想法呢?无非和数组下标和键值有关,看看题干“0 ≤火柴高度≤ maxlongint”,这就说明我们不可拿火柴高度作为树状数组下标,所以实现存下每根火柴在原数组中的下标A'和B',以火柴高度为关键字对数组A和B排一下序,根据A'为下标,把B'作为键值,放入数组C中,构成一个次序序列,再用C作为树状数组下标进行算法1操作。

问题解答完毕。

#include<cstdio>
#include<iostream>
#include<algorithm>
#define modnum 99999997
using namespace std;
int C[100001],D[100001],n,ans;
struct box{
    int id,key;
    bool operator<(const box h)const{
        return key<h.key;
    }
}A[100001],B[100001];
void read(box E[])
{
    for(int i=1;i<=n;i++){
        scanf("%d",&E[i].key);
        E[i].id=i;
    }
}
inline int lowbit(int x)
{return x&(-x);}
inline void add(int x)
{
    for(int i=x;i<=n;i+=lowbit(i))
        D[i]++;
}
inline int sum(int x)
{
    int cnt=0;
    for(int i=x;i>0;i-=lowbit(i))
        cnt+=D[i];
    return cnt;
}
int main()
{
    scanf("%d",&n);
    read(A);read(B);
    sort(A+1,A+n+1);sort(B+1,B+n+1);
    for(int i=1;i<=n;i++)
        C[A[i].id]=B[i].id;
    ans=0;
    for(int i=1;i<=n;i++){
        add(C[i]);
        ans+=(i-sum(C[i]));
        ans%=modnum;
    }
    printf("%d\n",ans%modnum);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值