【题目链接】
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=1∑n(ai−bi)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=1∑n(ai−bi)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=1∑n(ai−bi)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}) (ai−1,bi−1)
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} b1∼bi−1都已完成配对,而 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 a1∼ai都已完成配对,因此 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 (ai−bj)2+(ak−bi)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 (ai−bi)2+(ak−bj)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) (ai−bi)2+(ak−bj)2−(ai−bj)2−(ak−bi)2=(ai−bi)2−(ai−bj)2+(ak−bj)2−(ak−bi)2=(ai−bi+ai−bj)(ai−bi−ai+bj)+(ak−bj+ak−bi)(ak−bj−ak+bi)=(2ai−bi−bj)(bj−bi)+(2ak−bi−bj)(bi−bj)=(2ai−bi−bj)(bj−bi)−(2ak−bi−bj)(bj−bi)=(2ai−bi−bj−2ak+bi+bj)(bj−bi)=2(ai−ak)(bj−bi)
因为 a k > a i a_k>a_i ak>ai,所以 a i − a k < 0 a_i-a_k<0 ai−ak<0。
因为 b j > b i b_j>b_i bj>bi,所以 b j − b i > 0 b_j-b_i>0 bj−bi>0。
因此 2 ( a i − a k ) ( b j − b i ) < 0 2(a_i-a_k)(b_j-b_i)<0 2(ai−ak)(bj−bi)<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=1∑n(ai−bi)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
1∼n组成的序列。
记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 xk、xj二者之一与其他数构成的数对,
由于 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 xj、xk交换后也仍然会大于或小于 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 108−3取模。
解法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;
}