0x05 内容简介与例题习题

《算法竞赛进阶指南》读书笔记汇总
这里面是我在阅读《算法竞赛进阶指南》这本书时的一些思考,有兴趣可以瞧瞧!
如若发现什么问题,可以通过评论或者私信作者提出。希望各位大佬不吝赐教!

中位数

中位数有许多有趣的性质,我们来通过几道例题感受一下这些性质。

【例题】货仓选址(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=n1i=1nai,则 x i x_{i} xi必须满足下列以下等式:
a 1 + x n − x 1 = b a_{1} + x_{n} - x_{1} = b a1+xnx1=b
a 2 + x 1 − x 2 = b a _{2} + x_{1} - x_{2} = b a2+x1x2=b
. . . . . . ...... ......
a n + x n − 1 − x n = b a _{n} + x_{n - 1} - x_{n} = b an+xn1xn=b
通过以上等式,我们可以把 x i ( 1 ≤ i ≤ n − 1 ) x_{i}(1\le i \le n - 1) xi(1in1)表示成 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(ibj=1iai),(1in1)
那么我们的最终价值可以表示为:
∑ 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=1nxi=i=1n1xn(ibj=1iai)+xn0
那么我们可以发现,这个形式就是找出一个 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(2in)就会移动到 a + i − 1 a+ i - 1 a+i1位置。那么 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=1nxi(a+i1)=i=1nxi(i1)a
那么我们又可以利用中位数去算出这样一个 a a a使得总移动步数最小。
首先将 x x x坐标排序,将排完序后的每一个 x i x_{i} xi减去 i − 1 i - 1 i1,将处理完的 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;
}

未完成题目

【例题】七夕祭(AcWing105)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值