信息学奥赛一本通 1876:【13NOIP提高组】火柴排队 | 洛谷 P1966 [NOIP 2013 提高组] 火柴排队

【题目链接】

ybt 1876:【13NOIP提高组】火柴排队
洛谷 P1966 [NOIP 2013 提高组] 火柴排队

【题目考点】

1. 索引排序
2. 归并排序:求逆序对

原理见:洛谷 P1908 逆序对

3. 离散化

【解题思路】

1. 确定配对方案

a i a_i ai表示第一列火柴中第i个火柴的高度, b i b_i bi表示第二列火柴中第i个火柴的高度。
该题要在进行最少次交换相邻火柴的操作后,使两列火柴的距离 ∑ i = 1 n ( a i − b i ) 2 \sum\limits_{i=1}^n(a_i-b_i)^2 i=1n(aibi)2最小。

先给出配对方法:
a i a_i ai b j b_j bj配对为 ( a i , b j ) (a_i,b_j) (ai,bj)
将a、b序列进行升序排序, a i a_i ai表示a序列第i小的数, b i b_i bi表示b序列第i小的数,此时 ∑ i = 1 n ( a i − b i ) 2 \sum\limits_{i=1}^n(a_i-b_i)^2 i=1n(aibi)2是最小的。
即a序列中第1小的数 a 1 a_1 a1和b序列中第1小的数 b 1 b_1 b1配对,有 ( a 1 , b 1 ) (a_1,b_1) (a1,b1)
a序列中第2小的数 a 2 a_2 a2和b序列中第2小的数 b 2 b_2 b2配对,有 ( a 2 , b 2 ) (a_2,b_2) (a2,b2)

a序列第n小的数 a n a_n an和b序列中第n小的数 a n a_n an配对,,有 ( a n , b n ) (a_n,b_n) (an,bn)

该配对方法能得到最小的 ∑ i = 1 n ( a i − b i ) 2 \sum\limits_{i=1}^n(a_i-b_i)^2 i=1n(aibi)2

证明:
假设存在一个任意的配对方案,对所有的配对按照a从小到大排序。假设前i-1个配对都是 a a a的第x小的数与 b b b的第x小的数进行配对,有 ( a 1 , b 1 ) (a_1,b_1) (a1,b1) ( a 2 , b 2 ) (a_2,b_2) (a2,b2),…, ( a i − 1 , b i − 1 ) (a_{i-1},b_{i-1}) (ai1,bi1)
a i a_i ai参与的配对是第一个不是形如 ( a x , b x ) (a_x,b_x) (ax,bx)的配对。
a i a_i ai b j b_j bj配对,有 ( a i , b j ) (a_i,b_j) (ai,bj),由于 b 1 ∼ b i − 1 b_1\sim b_{i-1} b1bi1都已完成配对,而 a i a_i ai又不能和 b i b_i bi配对,因此 j > i j>i j>i,所以有 b j > b i b_j>b_i bj>bi
a k a_k ak b i b_i bi配对,有 ( a k , b i ) (a_k,b_i) (ak,bi),由于 a 1 ∼ a i a_1\sim a_i a1ai都已完成配对,因此 k > i k>i k>i,所以有 a k > a i a_k>a_i ak>ai
当前这两个数对对“火柴距离”的贡献为 ( a i − b j ) 2 + ( a k − b i ) 2 (a_i-b_j)^2+(a_k-b_i)^2 (aibj)2+(akbi)2
假设让这两个数对中 b i b_i bi b j b_j bj交换,得到 ( a i , b i ) , ( a k , b j ) (a_i,b_i),(a_k,b_j) (ai,bi)(ak,bj)两个数对。此时这两个数对对“火柴距离”的贡献为 ( a i − b i ) 2 + ( a k − b j ) 2 (a_i-b_i)^2+(a_k-b_j)^2 (aibi)2+(akbj)2
由于其余配对关系不变,因此将 b i b_i bi b j b_j bj交换后的贡献减去交换前的贡献,为火柴距离的变化
( a i − b i ) 2 + ( a k − b j ) 2 − ( a i − b j ) 2 − ( a k − b i ) 2 = ( a i − b i ) 2 − ( a i − b j ) 2 + ( a k − b j ) 2 − ( a k − b i ) 2 = ( a i − b i + a i − b j ) ( a i − b i − a i + b j ) + ( a k − b j + a k − b i ) ( a k − b j − a k + b i ) = ( 2 a i − b i − b j ) ( b j − b i ) + ( 2 a k − b i − b j ) ( b i − b j ) = ( 2 a i − b i − b j ) ( b j − b i ) − ( 2 a k − b i − b j ) ( b j − b i ) = ( 2 a i − b i − b j − 2 a k + b i + b j ) ( b j − b i ) = 2 ( a i − a k ) ( b j − b i ) (a_i-b_i)^2+(a_k-b_j)^2-(a_i-b_j)^2-(a_k-b_i)^2\\ =(a_i-b_i)^2-(a_i-b_j)^2+(a_k-b_j)^2-(a_k-b_i)^2\\ =(a_i-b_i+a_i-b_j)(a_i-b_i-a_i+b_j)+(a_k-b_j+a_k-b_i)(a_k-b_j-a_k+b_i)\\ =(2a_i-b_i-b_j)(b_j-b_i)+(2a_k-b_i-b_j)(b_i-b_j)\\ =(2a_i-b_i-b_j)(b_j-b_i)-(2a_k-b_i-b_j)(b_j-b_i)\\ =(2a_i-b_i-b_j-2a_k+b_i+b_j)(b_j-b_i)\\ =2(a_i-a_k)(b_j-b_i) (aibi)2+(akbj)2(aibj)2(akbi)2=(aibi)2(aibj)2+(akbj)2(akbi)2=(aibi+aibj)(aibiai+bj)+(akbj+akbi)(akbjak+bi)=(2aibibj)(bjbi)+(2akbibj)(bibj)=(2aibibj)(bjbi)(2akbibj)(bjbi)=(2aibibj2ak+bi+bj)(bjbi)=2(aiak)(bjbi)
因为 a k > a i a_k>a_i ak>ai,所以 a i − a k < 0 a_i-a_k<0 aiak<0
因为 b j > b i b_j>b_i bj>bi,所以 b j − b i > 0 b_j-b_i>0 bjbi>0
因此 2 ( a i − a k ) ( b j − b i ) < 0 2(a_i-a_k)(b_j-b_i)<0 2(aiak)(bjbi)<0
b i b_i bi b j b_j bj交换后,火柴距离会减小,为了得到最小的火柴距离,可以将二者交换。
完成交换后,有配对 ( a 1 , b 1 ) , . . . , ( a i , b i ) (a_1, b_1),...,(a_i,b_i) (a1,b1)...(ai,bi)
不断重复上述过程,每次交换都可以使得火柴距离 ∑ i = 1 n ( a i − b i ) 2 \sum\limits_{i=1}^n(a_i-b_i)^2 i=1n(aibi)2减小,同时形如 ( a x , b x ) (a_x,b_x) (ax,bx)的配对增加,最后得到的配对方案为 ( a 1 , b 1 ) , . . . , ( a n , b n ) (a_1,b_1),...,(a_n,b_n) (a1,b1),...,(an,bn)

2. 实现配对方案

解法1:离散化

要想进行最少的交换,完成配对方案 ( a 1 , b 1 ) , . . . , ( a n , b n ) (a_1,b_1),...,(a_n,b_n) (a1,b1),...,(an,bn)
可以先将a序列和b序列离散化,离散化后a、b都是由 1 ∼ n 1\sim n 1n组成的序列。
记a序列中数值i的下标为 i n d A i indA_i indAi,由于a序列中数值 a i a_i ai的下标为i,所以 i n d A a i = i indA_{a_i} = i indAai=i
在b序列中数值 i i i的下标为 i n d B i indB_i indBi,由于b序列中数值 b i b_i bi的下标为i,所以 i n d B b i = i indB_{b_i} = i indBbi=i
那么设数组 x x x x i x_i xi表示b序列中第 i i i个数 b i b_i bi在a序列中的下标,即 x i = i n d A b i x_i=indA_{b_i} xi=indAbi
需要在经过多次交换相邻元素后, b b b序列和 a a a序列相同,即 b i = a i b_i=a_i bi=ai
此时 x i = i n d A b i = i n d A a i = i x_i=indA_{b_i}=indA_{a_i}=i xi=indAbi=indAai=i,表示 b b b序列中第 i i i个数在 a a a序列中的下标为 i i i

证明:交换 b b b序列中相邻元素,最多会减少1对 x x x序列中的逆序对。

设当前配对有 ( a i , u ) (a_i,u) (ai,u) ( a i + 1 , v ) (a_{i+1},v) (ai+1,v)
其中 u u u b b b序列第i个数, v v v为b序列第i+1个数
此时 x i = i n d A u x_i = indA_{u} xi=indAu x i + 1 = i n d A v x_{i+1} = indA_{v} xi+1=indAv
交换b中第i个数和第i+1个数,得到数对 ( a i , v ) (a_i,v) (ai,v) ( a i + 1 , u ) (a_{i+1},u) (ai+1,u)
此时 x i = i n d A v x_i = indA_{v} xi=indAv x i + 1 = i n d A u x_{i+1} = indA_{u} xi+1=indAu
因此交换 b b b序列中相邻元素相应地会交换 x x x序列中的相邻元素。
交换 x x x序列中的一对相邻元素最多会减少一对x序列中的逆序对。
因此交换 b b b序列中相邻元素,最多会减少1对 x x x序列中的逆序对。

证明:交换 a a a序列中相邻元素,最多会减少1对 x x x序列中的逆序对。

设当前配对有 ( u , b i ) (u,b_i) (u,bi) ( v , b i + 1 ) (v,b_{i+1}) (v,bi+1) ( a j , u ) (a_j,u) (aj,u) ( a k , v ) (a_k,v) (ak,v)
其中 u u u a a a序列第i个数, b b b序列第j个数。 v v v为a序列第i+1个数, b b b序列第k个数。
此时 x j = i , x k = i + 1 x_j = i, x_k = i+1 xj=i,xk=i+1
交换a序列中第i个数和第i+1个数,得到数对 ( v , b i ) , ( u , b i + 1 ) (v,b_i),(u,b_{i+1}) (v,bi),(u,bi+1)
此时 x j = i + 1 , x k = i x_j=i+1,x_k=i xj=i+1,xk=i
相当于将 x j x_j xj x k x_k xk交换。
如果 j < k j<k j<k,数对 ( x j , x k ) (x_j,x_k) (xj,xk)构成的数对从 ( i , i + 1 ) (i,i+1) (i,i+1),变为 ( i + 1 , i ) (i+1,i) (i+1,i),增加了一个逆序对。
如果 j > k j>k j>k,数对 ( x k , x j ) (x_k,x_j) (xk,xj)构成的数对从 ( i + 1 , i ) (i+1,i) (i+1,i),变为 ( i , i + 1 ) (i,i+1) (i,i+1),减少了一个逆序对。
对于由 x k 、 x j x_k、x_j xkxj二者之一与其他数构成的数对,
由于 x j x_j xj x k x_k xk是相邻的两个整数,原来大于或小于 x j x_j xj(或 x k x_k xk)的数在 x j 、 x k x_j、x_k xjxk交换后也仍然会大于或小于 x j x_j xj(或 x k x_k xk),所以逆序对数量不变。
因此交换 a a a序列中相邻元素,最多会减少1对 x x x序列中的逆序对。

要让 x x x从初始序列变为 x i = i x_i=i xi=i,这是对 x x x序列的排序过程。通过交换 a a a序列或 b b b序列中相邻元素来减少 x x x序列中的逆序对,直到 x x x序列变为升序序列。
那么 a a a序列或 b b b序列中交换相邻元素的最少次数为 x x x序列中逆序对的数量
可以使用归并排序方法求 x x x序列中的逆序对数量,时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

由于序列长度n为 10 5 10^5 105,逆序对的数量为 n 2 n^2 n2量级,最大为 10 10 10^{10} 1010,可以使用long long类型变量保存逆序对数量,最后再对 10 8 − 3 10^8-3 1083取模。

解法2:索引数组

上述方法中使用离散化的方法将 a a a序列中第i小的数与 b b b序列中第i小的数转化为相同的数值i,而后再进行分析。
其实并不需要真的确定 a a a b b b序列第i小的数的数值。
可以在概念上去考虑使用 a a a b b b序列第 i i i小的数,在代码中使用索引数组实现。
假设 a a a序列排序后为序列 t a ta ta t a i ta_i tai a a a序列第i小的数。
a a a序列的索引数组为 i n d A indA indA i n d A i indA_i indAi t a i ta_i tai a a a序列中的下标,即 t a i = a i n d A i ta_i=a_{indA_i} tai=aindAi
假设 b b b序列排序后为序列 t b tb tb t b i tb_i tbi b b b序列第i小的数。
b b b序列的索引数组为 i n d B indB indB i n d B i indB_i indBi t b i tb_i tbi b b b序列中的下标,即 t b i = b i n d B i tb_i=b_{indB_i} tbi=bindBi
设数组 x x x x i x_i xi表示b序列中第 i i i个数 b i b_i bi在a序列中对应元素的下标。
b中第k小元素为: t b k = b i n d B k tb_k=b_{indB_k} tbk=bindBk
a中第k小元素为: t a k = a i n d A k ta_k=a_{indA_k} tak=aindAk
b i n d B k b_{indB_k} bindBk a i n d A k a_{indA_k} aindAk对应,因此 x i n d B k = i n d A k x_{indB_k}=indA_k xindBk=indAk
本方法中 x x x数组的概念和解法1中 x x x数组的概念一致,因此最终结果仍然是求 x x x数组的逆序对数量。

【题解代码】

解法1:离散化
#include<bits/stdc++.h>
using namespace std;
#define N 100005
const int M = 1e8-3;
int n, a[N], b[N], ind_a[N], t[N], x[N];
long long revNum;
void discretization(int *d, int n)//将传入的数组d中的数值离散化 
{
	vector<int> u(d+1, d+1+n);//复制d中元素到u中 
	sort(u.begin(), u.end());//没有重复元素 不需要去重
	for(int i = 1; i <= n; ++i)
		d[i] = upper_bound(u.begin(), u.end(), d[i])-u.begin();//离散化为1~n 
}
void mergeSort(int *a, int l, int r)//归并求a数组的逆序对 
{
	if(l >= r)
		return;
	int mid = (l+r)/2;
	mergeSort(a, l, mid);
	mergeSort(a, mid+1, r);
	int i = l, j = mid+1, k = l;
	while(i <= mid && j <= r)
	{
		if(a[i] > a[j])
		{
			t[k++] = a[j++];
			revNum += mid-i+1;
		}
		else
			t[k++] = a[i++];
	}
	while(i <= mid)
		t[k++] = a[i++];
	while(j <= r)
		t[k++] = a[j++];
	for(i = l; i <= r; ++i)
		a[i] = t[i];
}
int main()
{
	cin >> n;
	for(int i = 1; i <= n; ++i)
		cin >> a[i];
	for(int i = 1; i <= n; ++i)
		cin >> b[i];
	discretization(a, n);
	discretization(b, n);
	for(int i = 1; i <= n; ++i)
		ind_a[a[i]] = i;//ind_a[i]:数值i在a中的下标 
	for(int i = 1; i <= n; ++i)
		x[i] = ind_a[b[i]];//x[i]:b中第i个数b[i]在a中的下标
	mergeSort(x, 1, n);//求x数组逆序对数量 
	cout << revNum%M;
	return 0;
}
解法2:使用索引数组
#include<bits/stdc++.h>
using namespace std; 
#define N 100005 
const int M = 1e8-3;
int a[N], b[N], n, ind_a[N], ind_b[N], x[N], t[N];
long long revNum;
bool cmp_a(int x, int y)
{
	return a[x] < a[y];
}
bool cmp_b(int x, int y)
{
	return b[x] < b[y];
}
void mergeSort(int *a, int l, int r)
{
	if(l >= r)
		return;
	int mid = (l+r)/2;
	mergeSort(a, l, mid);
	mergeSort(a, mid+1, r);
	int i = l, j = mid+1, k = l;
	while(i <= mid && j <= r)
	{
		if(a[i] > a[j])
		{
			t[k++] = a[j++];
			revNum += mid-i+1;
		}
		else
			t[k++] = a[i++];
	}
	while(i <= mid)
		t[k++] = a[i++];
	while(j <= r)
		t[k++] = a[j++];
	for(i = l; i <= r; ++i)
		a[i] = t[i];
}
int main()
{
	cin >> n;
	for(int i = 1; i <= n; ++i)
	{
		cin >> a[i];
		ind_a[i] = i;
	}
	for(int i = 1; i <= n; ++i)
	{
		cin >> b[i];
		ind_b[i] = i;
	}
	sort(ind_a+1, ind_a+1+n, cmp_a);
	sort(ind_b+1, ind_b+1+n, cmp_b); 
    for(int i = 1; i <= n; ++i)
    	x[ind_b[i]] = ind_a[i];
    mergeSort(x, 1, n);
    cout << revNum%M;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值