[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个数字的原来位置
//将原队列排序后,把两个数列的离散数对齐
//以第一行数字位置为参照物
//重新排列第二行数的的位置
还看不懂的画个图自己体会。。