NOIP-2013火柴排队
题目描述
涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。 现在将每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 两列火柴之间的距离定义为: ∑(ai-bi)^2
其中 ai 表示第一列火柴中第 i 个火柴的高度,bi 表示第二列火柴中第 i 个火柴的高度。
每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对 99,999,997 取模的结果。
输入输出格式
输入格式:
输入文件为 match.in。
共三行,第一行包含一个整数 n,表示每盒中火柴的数目。
第二行有 n 个整数,每两个整数之间用一个空格隔开,表示第一列火柴的高度。
第三行有 n 个整数,每两个整数之间用一个空格隔开,表示第二列火柴的高度。
输出格式:
输出文件为 match.out。
输出共一行,包含一个整数,表示最少交换次数对 99,999,997 取模的结果。
输入输出样例
输入样例#1:
4
2 3 1 4
3 2 1 4
输出样例#1:
1
输入样例#2:
4
1 3 4 2
1 7 2 4
输出样例#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
题意概要
给定两条相等长度的数字序列a[]和b[],现在要求只能交换每一条队列中相邻的两个元素(如a[i]与a[i+1]可以交换,a[i]与b[i]则不能交换),交换次数不限,而交换或不交换所组成的序列中,求最小的∑ni=1(a[i]−b[i])2∑i=1n(a[i]−b[i])2
这让我想到上一次为了出试题而出的神题
这题同机房dalao们都没想出来,问了同校集训队队神,过了一星期了还没回复
把这题放到博客里,dalao可以想一想,蒟蒻求解答
好了,回归正题
思路
我们可以感性地想一想,只有两列数字所在自己的列的排名相等时上面那个式子才相等,我们可以证一证:
由于∑ni=1a[i]2+∑ni=1b[i]2∑i=1na[i]2+∑i=1nb[i]2是一个常数已知量,所以只要要求2∑ni−1a[i]·b[i]2∑i−1na[i]·b[i]最大即可,而那个2看起来特别讨厌,去掉也没关系,再用cy证明法
首先
1 3 5
2 4 6
可得1×2+3×4+5×6=44
而
5 3 1
2 4 6
得5×2+3×4+1×6=28
所以可以轻易地用cy证明法证明两条序列的值序相等时上述值最大,即可得题解
认识cy的人别传出去
所以我们只要将两条序列调成一致即可,但由于两条序列无先后之分,所以调整两条与只调整一条也是一样的
题做到这里,先喝杯茶,吃个包,站起来打下搏击操
继续
所以题目转化成了如何将第二条序列经过仅调整相邻元素,使得两条序列的排序相等,好像很像冒泡排序耶
那就是的,但是两条序列咋冒泡
可以将b树组的值转成a树组中存在的位置(这中间比较便捷的方式是先离散化,再扫描两条序列,用hash转换,具体见程序)
于是题目又转化为了求在一条序列中求逆序对的个数
(≥◇≤) 终于做完了
于是求逆序对的话依照个人习惯打归并或树状数组(本人打的是树状数组)
在看代码前恳请各位dalao看看我博客里的置顶的哪一题
AC代码
#include<bits/stdc++.h>
using namespace std;
#define lb(x) (-x&x)
#define mod 99999997
const int maxn=100050;
int n,a[maxn],b[maxn],c[maxn],hash[maxn];
template <typename _Tp> inline void read(_Tp &x){
char c11=getchar();x=0;bool booo=0;
while(c11<'0'||c11>'9'){if(c11=='-')booo=1;c11=getchar();}
while(c11>='0'&&c11<='9'){x=x*10+c11-'0';c11=getchar();}
if(booo) x=-x;
return ;
}
void init();
void pre();
void work();
int sz[maxn];
//以下俩函数dalao一看就知道是树状数组
inline int query(int p){int sum=0;for(int i=p;i;i-=lb(i))sum=(sum+sz[i])%mod;return sum;}
inline void insert(int p){for(int i=p;i<=n;i+=lb(i))sz[i]++;}
int lalala(){ //求逆序对数
int sum=0;
for(int i=n;i;i--){sum=(sum+query(b[i]))%mod;insert(b[i]);}
return sum;
}
int main(){
init();
pre();
work();
int aaa=lalala();
printf("%d",aaa);
return 0;
}
void work(){ //将两条链按排序序号转化成一条链
for(int i=1;i<=n;i++)hash[a[i]]=i;
for(int i=1;i<=n;i++)b[i]=hash[b[i]];
}
void pre(){ //离散化
for(int i=1;i<=n;i++)c[i]=a[i];
sort(c+1,c+n+1);
int m=unique(c+1,c+n+1)-c;
for(int i=1;i<=n;i++)a[i]=lower_bound(c+1,c+m+1,a[i])-c;
for(int i=1;i<=n;i++)c[i]=b[i];
sort(c+1,c+n+1);
m=unique(c+1,c+n+1)-c;
for(int i=1;i<=n;i++)b[i]=lower_bound(c+1,c+m+1,b[i])-c;
}
void init(){
read(n);
for(int i=1;i<=n;i++)read(a[i]);
for(int i=1;i<=n;i++)read(b[i]);
return ;
}