[NOIP·2013提高组] 火柴排队

【题目描述】

 

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

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

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

[Sample 1.in]    [Sample 1.out]
4                          1
2 3 1 4
3 2 1 4

[Sample 2.in]    [Sample 2.out]
4                         2
1 3 4 2
1 7 2 4



【题目分析】

特别亮眼的是那个距离的定义。它的含义是:求出(a1-a2)²+......+(an-bn)²。我们从样例入手,分析一下题。如下图:


我起初的想法,枚举如何交换,每一次算出距离,保留距离小且次数少的。用递归就可以轻易的实现。但是队列的长度小于等于100000,枚举肯定超时,拿不了多少分。

再想想,距离的算法两个队列元素对应相减的差相加,如果两个队列的元素完全一样,那么距离肯定是0(最小的)。但是出现这种情况的可能性实在是小的可怜。

如果我们能将这两个队列离散化,都用自然数映射,那这两个队列的元素就一样了。

问题来了,如何离散化呢?我可以先将两个队列拷贝下来,然后排序,最后将排列后队列的各个元素映射到原队列该元素的序数上就行。

我这样讲你们肯定是懵逼的,所以我来画个图让大家理解。


(这图会不会有点大)

然后找到第二个离散队列里的元素在第一个离散队列的序数(不将两个离散队列排序),我得到了一个队列:1 4 2 3(在第二个离散队列中,1是第一个离散队列里的第一个,3是第4个....)

求这个队列的逆序数(归并排序最快),就是我们要的答案。

附上代码。、

#include<cstdio>
#include<iostream>
#include<algorithm>
#define MAXN 100000
#define mod 99999997
using namespace std;
int a[MAXN+10],b[MAXN+10],c[MAXN+10],aa[MAXN+10],bb[MAXN+10],aaa[MAXN+10],bbb[MAXN+10],t[MAXN+10],ans;
void merge(int a[],int left,int mid,int right)
{
    int i=left,j=mid+1,k=left;
    while(i<=mid&&j<=right)
    {
        if(a[i]<=a[j])t[k++]=a[i++];
        else 
        {
            ans+=j-k;
            ans%=mod;
            t[k++]=a[j++];
        }
    }
    while(i<=mid)t[k++]=a[i++];
    while(j<=right)t[k++]=a[j++];
    for(i=left;i<=right;i++)
     a[i]=t[i];
    return ;
}
void mergesort(int a[],int left,int right)
{
    if(left<right)
    {
        int mid=(left+right)>>1;
        mergesort(a,left,mid);//分治 
        mergesort(a,mid+1,right);
        merge(a,left,mid,right);
    }
    return ;
}
int main()
{
    int n,i,j;
    ios::sync_with_stdio(false);
    cin>>n;
    for(i=1;i<=n;i++)cin>>a[i],aa[i]=a[i];
    for(i=1;i<=n;i++)cin>>b[i],bb[i]=b[i];
    sort(a+1,a+n+1);
    sort(b+1,b+n+1);
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=n;j++)
        {
            if(aa[i]==a[j])aaa[i]=j;
            if(bb[i]==b[j])bbb[i]=j;
        }
    }
    for(i=1;i<=n;i++)
     for(j=1;j<=n;j++)
        if(bbb[j]==aaa[i])
         {
             c[j]=i;
             continue;
         }
    mergesort(c,1,n);
    ans%=mod;
    cout<<ans;
    return 0;
}

(你以为这个代码就可以AC了吗?不存在的)
评测机直接甩给我一个TLE 80分。

各位老铁们没发现循环实在是太多了吗。。不爆时间才怪。

怎么样把中间一大段的循环减少一些,从而使时间效率增加呢?

贴上AC代码。(代码优化思路by石泽堃大佬)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define MAXN 100000
#define mod 99999997
using namespace std;
int c[MAXN+10],t[MAXN+10],ans;
struct node{
    int x,sx;
}a[MAXN+10],b[MAXN+10];
bool cmp(const node &a,const node &b)
{
    return a.x<b.x;
}
void merge(int a[],int left,int mid,int right)
{
    int i=left,j=mid+1,k=left;
    while(i<=mid&&j<=right)
    {
        if(a[i]<=a[j])t[k++]=a[i++];
        else 
        {
            ans+=j-k;
            ans%=mod;
            t[k++]=a[j++];
        }
    }
    while(i<=mid)t[k++]=a[i++];
    while(j<=right)t[k++]=a[j++];
    for(i=left;i<=right;i++)
     a[i]=t[i];
    return ;
}
void mergesort(int a[],int left,int right)
{
    if(left<right)
    {
        int mid=(left+right)>>1;
        mergesort(a,left,mid);//分治 
        mergesort(a,mid+1,right);
        merge(a,left,mid,right);
    }
    return ;
}
int main()
{
    int n,i,j;
    ios::sync_with_stdio(false);
    cin>>n;
    for(i=1;i<=n;i++)cin>>a[i].x,a[i].sx=i;
    for(i=1;i<=n;i++)cin>>b[i].x,b[i].sx=i;
    sort(a+1,a+n+1,cmp);
    sort(b+1,b+n+1,cmp);
    for(i=1;i<=n;i++)
    {
        c[a[i].sx]=b[i].sx;
    }
    mergesort(c,1,n);
    ans%=mod;
    cout<<ans;
    return 0;
}

(sx就是顺序,也就是序数)

p.s:sort要制定数据排列,不能够直接排列结构体,即使是升序,也必须要手写一个cmp告诉sort你要排结构体数组里的哪些值。

//a[i].sx>>第一列第i个数字的原来位置
  //b[i].sx>>第二列第i个数字的原来位置
  //将原队列排序后,把两个数列的离散数对齐
  //以第一行数字位置为参照物
  //重新排列第二行数的的位置

还看不懂的画个图自己体会。。

posted @ 2017-08-09 17:58  jiandanset  阅读(584)  评论(0编辑  收藏  举报