NOIP2013D1T2 火柴排队 题解

(题目描述略)

对于距离最小,其贪心策略为将两序列升序排序后比较。以下为简易的证明:
设对任意 a1 < a2,b1 < b2,
第一种排布是 ..a1a2..,..b1b2..,此时距离为 (a1 - b1) ^ 2 + (a2 - b2) ^ 2 (1),
第二种排布是 ..a1a2..,..b2b1..,此时距离为 (a1 - b2) ^ 2 + (a2 - b1) ^ 2 (2),
用(1)式减去(2)式,得差为 2(a1 - a2)(b2 - b1) < 0,
故(1)<(2),贪心策略得证。

于是,原题转化为,求最少的相邻交换次数使得两序列的大小顺序相同。考虑到两序列的大小顺序之间的关系不方便确定,我们可以用离散化的方法对序列预处理,再求其逆序对即可。

将序列预处理的过程如下:首先现将第一个序列中的值离散到整数区间 [1, n] 中,记为 order1[1..n],其中 order1[i] 表示第一个序列中下标为 i 的数值是序列中第 order1[i] 小的数值;再将第二个序列离散化,记为 order2[1..n],其中 order2[i] 表示第二个序列中第 i 小的数值的下标。

所以,我们可以发现,第一个序列中下标为 i 的数值对应第二个序列中下标为 order2[order1[i]] 的数值。于是,原题再次转化为,求最少的相邻交换次数使得序列 order2[order1[1..n]] 变成升序序列。对序列 order2[order1[1..n]] 求逆序对即为结果。

关于逆序对的求法,常见的有树状数组和归并排序两种。对于树状数组而言,我们令 i 升序依次在树状数组下标为 order2[order1[i]] 中插入 i,每加入一个数统计当前状态下在其前面的数,即统计 j 的个数使得 i > j 且 order2[order1[i]] > order2[order1[j]],在用 i 减去统计结果即为所有以 i 为起始的逆序对个数。求和即可。

对于归并排序而言,在每次对序列 order2[order1[i]] 归并的过程中,设前半段扫描指针 i,后半段扫描指针 j,后半段起始指针 m,若扫描中发现 order2[order1[i]] > order2[order1[j]],则逆序对计数器加上 m - i。因为此时对于任意 i < k < m,均有 order2[order1[k - 1]] < order2[order1[k]],所以对于任意 i ≤ k < m,均有 order2[order1[k]] > order2[order1[j]]。一旦发现,扫描指针 j 右移,故不会重复计算。每次归并计算出当前两段有序子序列之间的逆序对个数并将两段有序子序列合并成一段新的有序有序子序列,这样便能统计出所有原序列中的逆序对。

这两种算法的时间效率类似,均为 O(nlogn) 级别的算法;预处理中的离散化操作因用快排故时间效率也为 O(nlogn)。于是,总算法时间复杂度为 O(nlogn)。

代码如下:(树状数组版本)

#include"stdio.h"
#include"stdlib.h"
#include"string.h"
#define lowbit(x) ((x)&-(x))
int high[100005],sum[100005],order1[100005],order2[100005];
int cmp(const void *p,const void *q)
{
    return high[*(int *)p]>high[*(int *)q]?1:-1;
}
int main()
{
    freopen("match.in","r",stdin);
    freopen("match.out","w",stdout);
    int ans=0,n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&high[i]),
        order2[i]=i;
    qsort(order2+1,n,sizeof(int),cmp);
    for(int i=1;i<=n;i++)
        order1[order2[i]]=i;
    for(int i=1;i<=n;i++)
        scanf("%d",&high[i]),
        order2[i]=i;
    qsort(order2+1,n,sizeof(int),cmp);
    memset(sum,0,sizeof(sum));
    for(int i=1;i<=n;i++)
    {
        for(int j=order2[order1[i]];j<=n;j+=lowbit(j))
            sum[j]++;
        ans+=i;
        for(int j=order2[order1[i]];j>0;j-=lowbit(j))
            ans-=sum[j];
        ans%=99999997;
    }
    printf("%d",ans);
    return 0;
}

不得不说,本题离散化的思想有些复杂,但保持思路清晰仍然可解。当然,本题解只给出了树状数组版本的代码实现,对于归并排序版本的代码实现请读者自行探究。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值