0x17 内容简介与例题习题

《算法竞赛进阶指南》读书笔记汇总
这里面是我在阅读《算法竞赛进阶指南》这本书时的一些思考,有兴趣可以瞧瞧!
如若发现什么问题,可以通过评论或者私信作者提出。希望各位大佬不吝赐教!
许多问题都可以使用二叉堆来进行优化,下面直接看例题吧。

【例题】超市(AcWing145)

题目链接
思路: 容易想到一个贪心策略:对于每个时间 t t t,在保证不卖出过期商品的情况下,尽量卖出利润前 t t t大的商品。具体做法如下:
1.我们将所有商品按照过期时间从小到大排序,建立一个初始为空的小根堆,然后依次扫描每一个商品。
2.对于当前商品,如果当前过期时间等于堆中商品数目,说明前 t t t天已经安排了 t t t个未过期商品准备卖出,那么如果堆顶商品利润小于当前商品利润,则用当前商品替换掉堆顶。
3.若当前商品的过期时间大于堆中商品数目,直接把该商品插入堆中。
4.最终,堆中的商品就是我们所要出售的商品啦。
可以体会一下维护堆的过程。

AC代码:

#include<bits/stdc++.h>

using namespace std;

typedef pair<int,int> PII;
const int N = 10005;

int n;
PII goods[N];

void solve(){
    for(int i = 1;i <= n;i ++)
    {
        int p,d;
        scanf("%d%d",&p,&d);
        goods[i] = {d,p};
    }
    
    sort(goods + 1,goods + 1 + n);
    
    priority_queue<int,vector<int>, greater<int> > q;
    for(int i = 1;i <= n;i ++){
        if(q.size() < goods[i].first) q.push(goods[i].second);
        else if(q.top() < goods[i].second) q.pop(),q.push(goods[i].second);
    }

    int ans = 0;
    while(q.size()){
        ans += q.top();
        q.pop();
    }
    
    printf("%d\n",ans);
}

int main(){
    while(~scanf("%d",&n))
        solve();
    return 0;
}

【例题】序列(AcWing146)

题目链接
思路:我们考虑这样一个简化问题,即 M = 2 M=2 M=2时,问题变为从两个长度为 N N N的序列中找出 N N N对和最小的数。
假设两个序列分别为 A A A B B B,我们先把 A A A序列从小到大排序,然后对于所有的数对(共有 N 2 N^{2} N2个),考虑一个分组的思想(来自y总)。
分别为:
B [ 1 ] + A [ 1 ] , B [ 1 ] + A [ 2 ] , . . . , B [ 1 ] + A [ N ] B[1] + A[1],B[1]+A[2],...,B[1]+A[N] B[1]+A[1],B[1]+A[2],...,B[1]+A[N]
B [ 2 ] + A [ 1 ] , B [ 2 ] + A [ 2 ] , . . . , B [ 2 ] + A [ N ] B[2]+A[1],B[2]+A[2],...,B[2]+A[N] B[2]+A[1],B[2]+A[2],...,B[2]+A[N]
. . . . . . ...... ......
B [ N ] + A [ 1 ] , B [ N ] + A [ 2 ] , . . . , B [ N ] + A [ N ] B[N]+A[1],B[N]+A[2],...,B[N]+A[N] B[N]+A[1],B[N]+A[2],...,B[N]+A[N]
我们发现,每一组数对里,数对和都是单调不降的,那么我们可以利用一个小根堆维护两个指针和一个数对和,也就是一个三元组 i , j , s u m {i,j,sum} i,j,sum i , j i,j i,j分别表示指向 B B B数组与 A A A数组的指针。初始把每一组的第一个数对放进小根堆中,每次取出堆顶元素(当前的最小数),把它的 j j j指针加一,将得到的新数对放进小根堆中。重复取出 N N N即为前 N N N小的数对和。
那么如果要把 M M M个序列合并,那么我们可以从第一个序列开始一个一个向下合并,不难证明这个做法的正确性。

AC代码:

#include<bits/stdc++.h>

using namespace std;

const int N = 2005;

int n,m;
int a[N][N];
int tmp[N];

struct node{
    int i,j,sum;
    bool operator < (const node& b)const{
        return sum > b.sum;
    }
};

void solve(){
    cin >> m >> n;
    for(int i = 1;i <= m;i ++)
        for(int j = 1;j <= n;j ++)
            cin >> a[i][j];
            
    sort(a[1] + 1,a[1] + 1 + n);
    
    for(int i = 2;i <= m;i ++){
        priority_queue<node> q;
        for(int j = 1;j <= n;j ++){
            q.push(node{j,1,a[i][j] + a[i - 1][1]});          
        }
        for(int j = 1;j <= n;j ++){
            node cur = q.top();
            q.pop();
            tmp[j] = cur.sum;
            q.push(node{cur.i,cur.j + 1,a[i][cur.i] + a[i - 1][cur.j + 1]});
        }
        memcpy(a[i],tmp,sizeof(tmp));
    }
    
    for(int i = 1;i <= n;i ++){
        if(i != 1) printf(" ");
        printf("%d",a[m][i]);
    }
    puts("");
}

int main(){
    int t;cin >> t;
    while(t --)
        solve();
    return 0;
}

下面再看看一道习题。

【练习】黑盒子(AcWing162)

题目链接
思路:我们可以使用一个大根堆和一个小根堆来维护,大根堆维护排名前 i − 1 i-1 i1个数,小根堆维护排名在 i i i以及之后的数。对于 G E T GET GET操作,我们只需要把小根堆堆顶输出,再把小根堆堆顶放到大根堆中。对于 A D D ADD ADD操作,如果当前数大于或等于小根堆堆顶(即 A [ i ] A[i] A[i]),则把当前数放到大根堆中;如果如果当前数小于大根堆堆顶,那么把当前数放到小根堆中,再把小根堆堆顶放到大根堆中。这题思想就是一个对顶堆,与动态维护中位数那题类似。

AC代码:

#include<bits/stdc++.h>
#define LL long long 
using namespace std;

const int N = 30005;

int n,m;
LL a[N];
int u[N];

void solve(){
    cin >> n >> m;
    for(int i = 1;i <= n;i ++)
        cin >> a[i];
    for(int i = 1;i <= m;i ++)
        cin >> u[i];
    
    priority_queue<LL, vector<LL>, greater<LL> > q1; // 小顶
    priority_queue<LL> q2; // 大顶
    int i = 0, k = 1;
    for(int j = 1;j <= n;j ++){
        if(q1.empty() || a[j] >= q1.top()) q1.push(a[j]);
        else{
            q2.push(a[j]);
            q1.push(q2.top());
            q2.pop();
        }
        while(j == u[k]){
            i ++;
            q2.push(q1.top());
            cout << q1.top() << endl;
            q1.pop();
            k ++;
        }
    }
    
}

int main(){
    solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值