火柴排队 题解

\(1.\) 题目关键点

这道题其实就是一道求 逆序对 的题

那为什么是求逆序对呢?

我们来分析一下题目:

题目要求交换完以后要 \(\sum\limits_{i=1}^n (a_i - b_i)^2\) 最小

拆分得

\[\sum\limits_{i=1}^n (a_i - b_i)^2 = \sum\limits_{i=1}^n ({a_i}^ 2+ {b_{i}}^ 2 -2a_i b_i) = \sum\limits_{i=1}^n ({a_{i}}^2+{b_{i}}^2)-2\sum\limits_{i=1}^n (a_i b_i) \]

因为 \({a_i}^2 + {b_i}^2\) 是固定的,所以最小值就在 \(a_i b_i\) 上,即 \(a_i,b_i\) 越大,代数式的值就越小

那当 \(\sum\limits_{i=1}^n (a_i b_i)\) 最大时,便是本题最优解

那如何取最大值?

这里先给出一个策略(证明放在后面):

对于数列 \(p_1 \sim p_n,q_1 \sim q_n\)

\[\max \{\sum\limits_{i=1}^n (p_i q_i) \} = \sum\limits_{i=1}^n (p_i q_i) \qquad\qquad\qquad (p_1 \le p_2 \le p_3 ... p_{n-1} \le p_n,q_1 \le q_2 \le q_3 \le ... q_{n-1} \le q_n) \]

或者上面公式中 \(p,q\) 按降序排列也行。

其实上面公式中所表示的含义就是 顺序之乘 \(\geq\) 乱序之乘

接下来证明一下 如果有人直接想看代码可以 跳过(bushi

\(1.\) 设升序数列 \(p_1 \sim p_n,q_1 \sim q_n\) 其中 \(0< p_1 < p_2,0< q_1 < q_2\)

\(2.\) 推论过程,先给出一个结论:

\[p_1 q_1 +p_2 q_2> p_1 q_2 +p_2 q_1 \]

\(3.\) 证明这个结论:

\[\text{移项,得} \quad p_1 q_1 +p_2 q_2 - p_1 q_2 - p_2 q_1 >0 \\ \\ \]

\[\text{合并同类项,得} \quad p_1(q_1 - q_2) +p_2(q_2 - q_1) >0 \\ \\ \]

\[\text{转换,得} \quad p_1(q_1 - q_2) - p_2(q_1 - q_2) >0 \\ \\ \]

\[\text{再合并同类项,得} \quad (p_1 - p_2)(q_1 - q_2)>0 \\ \\ \]

\[\because p_1 < p_2 ,q_1 < q_2 \quad \therefore p_1-p_2<0 , q_1 - q_2<0 \\ \\ \]

\[\therefore (p_1 - p_2)(q_1 - q_2)>0 \\ \\ \]

\[\text{由} (p_1 - p_2)(q_1 - q_2)>0 \text{向上推,最终得出} \\ \\ \]

\[p_1 q_1 +p_2 q_2> p_1 q_2 +p_2 q_1 \\ \\ \]

因此该公式成立

\(4.\) 推广该结论到整个数列中,乱序之乘就相当于不断将交换顺序打乱的过程,最终符合该结论的标准,最终得出 顺序之乘 \(\geq\) 乱序之乘证毕

那么,证明好这个结论,我们继续往下看

由题目要 \(\sum\limits_{i=1}^n (a_i b_i)\) 最大,我们由上面的公式推导可以想出:先给 \(a,b\) 分别从小到大排序,让 \(a\) 中第一小的数对 \(b\) 中第一小的数,让 \(a\) 中第二小的数对 \(b\) 中第二小的数……以此类推

那最小距离就出来了,可是我们怎么求出最小交换次数呢?

我们举一个例子 其实就是样例1

4
2 3 1 4
3 2 1 4

我们由样列知,\(a\) 数组为 \(a = \{2,3,1,4 \}\)\(b\) 数组为 \(b = \{3,2,1,4 \}\)

对两个数组分别从小到大排序,得

信息 \(\backslash\) 数组 \(a\) \(b\)
排序过后的值 \(\{1,2,3,4\}\) \(\{1,2,3,4\}\)
该数组中该值对应的原数组的编号 \(\{3,1,2,4\}\) \(\{3,2,1,4\}\)

我们来依次处理一下:

首先看 \(a_1\) 的编号与 \(b_1\) 的编号,都是 \(3\),因此不用管

再来看一下 \(a_2\) 的编号与 \(b_2\) 的编号,\(a_2\) 编号是 \(1\)\(b_2\) 编号是 \(2\),是一个逆序对,不符合“相同排名一一对应”的原则

这里,产生了不符合原则的数对,所以我们可以另外定义一个数组 \(ma\),以进行演算。这也是本程序的核心

我们看一下 \(ma\) 如何工作的:

首先,设 \(ma_{b_i \text{的编号}} = a_i \text{的编号}\)

还是以上面的样例为例,操作是这样的:

  1. \(ma_{b_1 \text{的编号}} = a_1 \text{的编号} \rightarrow ma_3=3\)
  2. \(ma_{b_2 \text{的编号}} = a_2 \text{的编号} \rightarrow ma_2=1\)
  3. \(ma_{b_3 \text{的编号}} = a_3 \text{的编号} \rightarrow ma_1=2\)
  4. \(ma_{b_4 \text{的编号}} = a_4 \text{的编号} \rightarrow ma_4=4\)
  5. 所以 \(ma= \{2,1,3,4\}\),逆序对个数为 \(1\)(交换 \(ma_1\)\(ma_2\)

看,这就是 排序 的魅力!

为什么上述操作能够实现?

因为产生了 逆序:只要原序列中的 \(a_i\)\(b_i\) 的排名是一一对应的,那么排序以后两个数的 相对位置不变,因此不会产生逆序;而如果不是一一对应的,排完序两个数的 相对位置就会发生变化,因而产生逆序对

求逆序对有多种方法:冒泡、归并、树状数组等

这里由于 本蒟蒻太菜,只会冒泡和归并,冒泡又会超时,所以用归并写的

如果不知道归并或想用树状数组写可以自行 出门右转 百度

好了,那么思路就推导完了,接下来如果读者已经懂了可以自己尝试写一下

如果还想看代码,也可以往后面看(bushi


\(2.\) 代码实现

本部分将分步骤写成

\(2.1\) 定义与模板

这部分比较简单,代码里有注释

//#pragma GCC optimize(3)
//#pragma GCC optimize(2)

//上方是O(2)与O(3)的优化,如果超时可以尝试开一下(但是本程序不会)
//要注意的是,上方代码不可以再洛谷取消注释后提交,不然会CE(别问我怎么知道的)

#include<bits/stdc++.h>//万能头
using namespace std;//命名空间
const int mod=99999997;//按题目定义的模数
int n,ans=0;//ans指最少要交换几次
struct match{//定义结构体match(火柴)
	int high,id;//high指火柴高度,id指火柴排序后在原来数列的编号
	inline bool operator < (const match &temp)//重载运算符,不会的自己百度
		const{return high<temp.high;}//按照高度从小到大排序
}a[100009],b[100009];
int ma[100009],temp[100009];//ma的定义在上文,temp是对ma归并排序时的临时数组
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0); //玄学的cin,cout优化
	return 0;
} 

\(2.2\) 预处理

预处理包含输入、离散化、排序、初始化好 \(ma\) 数组的值

//#pragma GCC optimize(3)
//#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int mod=99999997;
int n,ans=0;
struct match{
	int high,id;
	inline bool operator < (const match &temp)
		const{return high<temp.high;}
}a[100009],b[100009];
int ma[100009],temp[100009];
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0); 
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i].high,a[i].id=i;
	for(int i=1;i<=n;i++) cin>>b[i].high,b[i].id=i;//输入,由于数据太大,需要离散化
	sort(a+1,a+n+1),sort(b+1,b+n+1);//对火柴排序
	for(int i=1;i<=n;i++) ma[b[i].id]=a[i].id;//ma的用法上问所示
	return 0;
} 

\(2.3\) 归并排序求逆序对及输出

归并排序……都写到 \(\text{蓝题}\) 就不用我讲了吧……

不过如果想知道归并排序可以看看 这些

只是模板罢了

//#pragma GCC optimize(3)
//#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
const int mod=99999997;
int n,ans=0;
struct match{
	int high,id;
	inline bool operator < (const match &temp)
		const{return high<temp.high;}
}a[100009],b[100009];
int ma[100009],temp[100009];
inline void Sort(int l,int r)//归并排序模板,我就不多说了
{
	if(l>=r) return;
	int mid=(l+r)>>1;
	Sort(l,mid),Sort(mid+1,r);
	int i=l,j=mid+1,k=l;
	while(i<=mid&&j<=r)
	{
		if(ma[i]<=ma[j]) temp[k++]=ma[i++];
		else temp[k++]=ma[j++],ans+=(mid-i+1)%mod,ans%=mod;
	}
	while(i<=mid) temp[k++]=ma[i++];
	while(j<=r) temp[k++]=ma[j++];
	for(int point=l;point<=r;point++) ma[point]=temp[point];
	return;
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0); 
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i].high,a[i].id=i;
	for(int i=1;i<=n;i++) cin>>b[i].high,b[i].id=i;
	sort(a+1,a+n+1),sort(b+1,b+n+1);
	for(int i=1;i<=n;i++) ma[b[i].id]=a[i].id;
	Sort(1,n);//对ma进行归并
	cout<<ans;//输出,不需要我讲了吧
	return 0;
} 

\(2.4\) 最终无注释 高清 代码

这个我就不放在这里了吧,毕竟前面已经演示过很多次了

想知道的可以看 这里


\(3.\) 总结

知识点:不等式、归并求逆序对、离散化等

题目涉及知识点不难,只是要慢慢细心地推,发现求逆序对的 本质


\(\huge\texttt{THE END}\)


posted @ 2022-08-24 17:50  DreamerX  阅读(104)  评论(0)    收藏  举报