排序
《算法竞赛进阶指南》读书笔记汇总
这里面是我在阅读《算法竞赛进阶指南》这本书时的一些思考,有兴趣可以瞧瞧!
如若发现什么问题,可以通过评论或者私信作者提出。希望各位大佬不吝赐教!
中位数
中位数有许多有趣的性质,我们来通过几道例题感受一下这些性质。
【例题】货仓选址(AcWing104)
题目链接
思路: 因为这个专题是中位数的专题,那么我们可以猜到,这个地址选在所有商店位置的中位数处最优。为什么呢?我们假设最优解不是在中位数处,假设在中位数的左边,如果我们把这个解往右边移动的话,总距离会减小;假设在中位数的右边,如果我们把这个解往左边移动的话,总距离也会减小。那么最优解肯定是在中位数处。
设商店数量为
N
N
N,第
i
i
i个商店的位置为
p
o
s
[
i
]
pos[i]
pos[i],那么如果
N
N
N是偶数的话,最优解的位置为
p
o
s
[
N
/
2
]
pos[ N / 2 ]
pos[N/2]到
p
o
s
[
N
/
2
+
1
]
pos[N / 2 + 1]
pos[N/2+1]之间的任意位置;如果
N
N
N是奇数的话,最优解的位置为
p
o
s
[
(
N
+
1
)
/
2
]
pos[( N + 1 ) / 2]
pos[(N+1)/2]。那么无论
N
N
N是奇数还是偶数,其实最优解的位置可以设置为
p
o
s
[
(
N
+
1
)
/
2
]
pos[(N + 1) / 2]
pos[(N+1)/2]。
AC代码:
#include<iostream>
#include<algorithm>
#define N 100005
#define LL long long
using namespace std;
int n;
int a[N];
int main(){
cin >> n;
for(int i = 1;i <= n;i ++)
cin >> a[i];
sort(a + 1,a + 1 + n);
LL ans = 0;
for(int i = 1;i <= n;i ++){
ans += abs(a[(n + 1) / 2] - a[i]);
}
cout << ans << endl;
return 0;
}
【例题】动态中位数(AcWing106)
题目链接
思路: 动态维护中位数是一个经典问题。我们可以使用一个“对顶堆”的在线做法,首先建立一个小顶堆和一个大顶堆,然后依次读入
N
N
N。在读入过程中,设当前已读序列长度为
M
M
M,我们只需要保持序列中从小到大排名为
1
1
1~
M
/
2
M / 2
M/2的整数存储在大根堆中,保持序列中从小到大排名为
M
/
2
+
1
M / 2 + 1
M/2+1~
M
M
M的整数存储在小根堆中。那么当序列长度为奇数时,小根堆堆顶的整数就是我们需要输出的数。
任何时候,如果某一个堆中的元素过多,就从堆顶拿出放进另一个堆中。
AC代码:
#include<bits/stdc++.h>
#define LL long long
#define N 1005
using namespace std;
int p;
int m;
int id;
void solve(){
cin >> p;
for(int i = 1;i <= p;i ++){
cin >> id >>m;
priority_queue<int> q1;
priority_queue<int,vector<int>, greater<int> > q2;
vector<int> ans;
for(int j = 1;j <= m;j ++){
int x;cin>> x;
if(q2.size() == 0) q2.push(x);
else if(x > q2.top()) q2.push(x);
else q1.push(x);
if(q2.size() > q1.size() && q2.size() - q1.size() >= 2){
q1.push(q2.top());
q2.pop();
}
else if(q1.size() > q2.size() && q1.size() - q2.size() >= 2){
q2.push(q1.top());
q1.pop();
}
else if(q1.size() > q2.size() ){
q2.push(q1.top());
q1.pop();
}
if(j & 1)
ans.push_back(q2.top());
}
cout << id << " " << ans.size() << endl;
int cnt = 0;
for(auto x : ans){
cnt ++;
if(cnt == 10){
cout << x << "\n";
cnt = 0;
}
else
cout << x << " ";
}
if(cnt) cout << endl;
}
}
int main(){
solve();
return 0;
}
习题
【练习】糖果传递(AcWing122)
题目链接
思路: 我们令第
i
i
i个小朋友的糖果数量为
a
[
i
]
a[i]
a[i],令第
i
i
i个小朋友给第
(
i
+
1
)
(i + 1)
(i+1)个小朋友的糖果数量为
x
i
x_{i}
xi(第
n
n
n个小朋友给第
1
1
1个小朋友),令
b
=
1
n
∑
i
=
1
n
a
i
b = \frac{1}{n} \sum_{i = 1}^na_{i}
b=n1∑i=1nai,则
x
i
x_{i}
xi必须满足下列以下等式:
a
1
+
x
n
−
x
1
=
b
a_{1} + x_{n} - x_{1} = b
a1+xn−x1=b
a
2
+
x
1
−
x
2
=
b
a _{2} + x_{1} - x_{2} = b
a2+x1−x2=b
.
.
.
.
.
.
......
......
a
n
+
x
n
−
1
−
x
n
=
b
a _{n} + x_{n - 1} - x_{n} = b
an+xn−1−xn=b
通过以上等式,我们可以把
x
i
(
1
≤
i
≤
n
−
1
)
x_{i}(1\le i \le n - 1)
xi(1≤i≤n−1)表示成
x
n
x_{n}
xn与已知的
a
a
a数组元素的关系,他们的关系为:
x
i
=
x
n
−
(
i
∗
b
−
∑
j
=
1
i
a
i
)
,
(
1
≤
i
≤
n
−
1
)
x_{i}=x_{n}-(i * b - \sum_{j = 1}^ia_{i}),(1\le i \le n - 1)
xi=xn−(i∗b−∑j=1iai),(1≤i≤n−1)
那么我们的最终价值可以表示为:
∑
i
=
1
n
∣
x
i
∣
=
∑
i
=
1
n
−
1
∣
x
n
−
(
i
∗
b
−
∑
j
=
1
i
a
i
)
∣
+
∣
x
n
−
0
∣
\sum_{i = 1}^n|x_{i}|=\sum_{i = 1}^{n - 1}|x_{n}-(i *b - \sum_{j =1}^ia_{i})| + |x_{n}-0|
∑i=1n∣xi∣=∑i=1n−1∣xn−(i∗b−∑j=1iai)∣+∣xn−0∣
那么我们可以发现,这个形式就是找出一个
x
n
x_{n}
xn,使得所有点与这个
x
n
x_{n}
xn的总距离最小。我们利用中位数的性质,可以轻易的求出这个
x
n
x_{n}
xn,即可求出最小价值。
AC代码:
#include<bits/stdc++.h>
#define N 1000005
#define LL long long
using namespace std;
LL a[N];
int main(){
int n;
LL sum = 0;
cin >> n;
for(int i = 1;i <= n;i ++){
cin >> a[i];
sum += a[i];
}
LL avr = sum / n;
vector<LL> v;
v.push_back(0LL);
sum = 0;
for(int i = 1;i < n;i ++){
sum += a[i];
v.push_back(i * avr - sum);
}
sort(v.begin(),v.end());
LL ans = 0;
for(int i = 0;i < v.size();i ++){
ans += abs(v[i] - v[v.size() / 2]);//这里不用(v.size() + 1) / 2,是因为数组下标从0开始
}
cout << ans << endl;
return 0;
}
【练习】士兵(AcWing123)
题目链接
思路: 首先不难发现两个方向是独立的。对于
y
y
y方向的话,我们利用中位数可以快速求出。对于
x
x
x方向的话,首先按照
x
x
x坐标从小到大排序,那么我们不难证明,最优移动前的相对位置与最优移动后的相对位置是一样的。我们假设最终
x
1
x_{1}
x1移动到了
a
a
a这个位置,那么
x
i
(
2
≤
i
≤
n
)
x_{i}(2\le i \le n)
xi(2≤i≤n)就会移动到
a
+
i
−
1
a+ i - 1
a+i−1位置。那么
x
x
x方向的总移动步数可以表示为:
∑
i
=
1
n
∣
x
i
−
(
a
+
i
−
1
)
∣
=
∑
i
=
1
n
∣
x
i
−
(
i
−
1
)
−
a
∣
\sum_{i = 1}^n|x_{i} - (a + i - 1)|=\sum_{i =1}^n|x_{i} - (i - 1) -a|
∑i=1n∣xi−(a+i−1)∣=∑i=1n∣xi−(i−1)−a∣
那么我们又可以利用中位数去算出这样一个
a
a
a使得总移动步数最小。
首先将
x
x
x坐标排序,将排完序后的每一个
x
i
x_{i}
xi减去
i
−
1
i - 1
i−1,将处理完的
x
x
x坐标再排序一遍,利用中位数的性质即可算出答案。
AC代码:
#include<bits/stdc++.h>
#define N 10005
#define LL long long
using namespace std;
int n;
int x[N],y[N];
void solve(){
cin >> n;
vector<int> vx;
for(int i = 1;i <= n;i ++)
cin >> x[i] >> y[i];
sort(x + 1,x + 1 + n);
int ans = 0;
sort(y + 1,y + 1 + n);
for(int i = 1;i <= n;i ++)
ans += abs(y[i] - y[(n + 1) / 2]);
for(int i = 1;i <= n;i ++)
x[i] -= (i - 1);
sort(x + 1,x + 1 + n);
for(int i = 1;i <= n;i ++)
ans += abs(x[i] - x[(n + 1) / 2]);
cout << ans << endl;
}
int main(){
solve();
return 0;
}