算法刷题day25:多路归并

本文详细分析了如何使用多路归并算法解决钓鱼问题(考虑路程和时间)、技能升级问题(通过二段性策略优化),展示了实际编程中的应用场景。
摘要由CSDN通过智能技术生成

引言

关于这个多路并归蓝桥杯考的不是很多,如果要出的话,可能模型都会差不多,因为不会出太难的题,难题基本上都是贪心、DP之类的,所以好好刷题刷熟练就行了,见过熟悉即可,加油!


概念

多路归并:首先可以参考博客算法刷题:归并排序归并排序,这个多路归并其实就是一种类型的题目,还是要因题而异。


一、鱼塘钓鱼

标签:多路归并

思路: 1. 1. 1. 首先核心就是则个人钓鱼不会折返跑,因为每个鱼塘钓的鱼的数量是和你钓的次数有关,和时间无关,如果你又折返跑回来钓的话,还不如就直接在上一次的鱼塘多钓几次,还浪费路上的时间,所以最优解肯定是一条直线,而不会是来回的跑,因为这个 N N N 比较小,所以我们可以直接枚举最远的池塘数来计算最大值。 2. 2. 2. 我们可以提前把路程减去,然后只剩下钓鱼的时间了,所以就相当于在这多个鱼塘里钓,由于钓一次都是一分钟,并且没有路程的计算,所以就挑最大的几个就行了,也就是多个数 a [ i ] a[i] a[i] ,每个数取了就递减 d [ i ] d[i] d[i] 个,求最多能取多少个数,直接拿堆来模拟即可。

题目描述:

有 N 个鱼塘排成一排,每个鱼塘中有一定数量的鱼,例如:N=5 时,如下表:

鱼塘编号	                                       1	2	3	4	5
第1分钟能钓到的鱼的数量(1..1000)	              10	14	20	16	9
每钓鱼1分钟钓鱼数的减少量(1..100)	               2	4	6	5	3
当前鱼塘到下一个相邻鱼塘需要的时间(单位:分钟)	3	5	4	4	
即:在第 1 个鱼塘中钓鱼第 1 分钟内可钓到 10 条鱼,第 2 分钟内只能钓到 8 条鱼,……,第 5 分钟以后再也钓不到鱼了。

从第 1 个鱼塘到第 2 个鱼塘需要 3 分钟,从第 2 个鱼塘到第 3 个鱼塘需要 5 分钟,……

给出一个截止时间 T,设计一个钓鱼方案,从第 1 个鱼塘出发,希望能钓到最多的鱼。

假设能钓到鱼的数量仅和已钓鱼的次数有关,且每次钓鱼的时间都是整数分钟。

输入格式
共 5 行,分别表示:
第 1 行为 N;
第 2 行为第 1 分钟各个鱼塘能钓到的鱼的数量,每个数据之间用一空格隔开;
第 3 行为每过 1 分钟各个鱼塘钓鱼数的减少量,每个数据之间用一空格隔开;
第 4 行为当前鱼塘到下一个相邻鱼塘需要的时间;
第 5 行为截止时间 T。

输出格式
一个整数(不超过231−1),表示你的方案能钓到的最多的鱼。

数据范围
1≤N≤100 ,1≤T≤1000
输入样例:
5
10 14 20 16 9
2 4 6 5 3
3 5 4 4
14
输出样例:
76

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 110;

int n, T;
int a[N], d[N], l[N];

LL work(int x, int t)
{
    if(t <= 0) return 0;
    priority_queue<PII> heap;
    for(int i = 1; i <= x; ++i) heap.push({a[i], i});
    
    LL res = 0;
    while(t-- && heap.size())
    {
        auto t1 = heap.top(); heap.pop();
        int v = t1.first, p = t1.second;
        res += v;
        if(v - d[p] > 0) heap.push({v-d[p], p});
    }
    
    return res;
}

int main()
{
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    
    cin >> n;
    for(int i = 1; i <= n; ++i) cin >> a[i];
    for(int i = 1; i <= n; ++i) cin >> d[i];
    for(int i = 2; i <= n; ++i) 
    {
        cin >> l[i];
        l[i] += l[i-1];
    }
    
    cin >> T;
    LL res = 0;
    for(int i = 1; i <= n; ++i)
    {
        res = max(res, work(i, T - l[i]));
    }
    
    cout << res << endl;
}

二、技能升级

标签:多路归并

思路:明显可以看出这道题是上一道题的简化版,也就是这道题是包含在上道题里的,也就是 w o r k work work 函数,但由于这道题的 M M M 的值比较大,所以如果用 h e a p heap heap 来做的话,能过 7 12 \frac{7}{12} 127 个数据,所以要另辟蹊径了。我们假设所有的可能的数第 M M M 个数值为 mid ,那么最大值就是取前 M M M 个数了,所以我们只要 mid 确定了下来,剩余就可以遍历每个序列用数学公式就能计算出来总和。然后我们能够知道小于等于 mid 的个数一定是大于等于 M 的,所以具有二段性可以用二分来写,具体细节见代码。

题目描述:

小蓝最近正在玩一款 RPG 游戏。

他的角色一共有 N 个可以加攻击力的技能。

其中第 i 个技能首次升级可以提升 Ai 点攻击力,以后每次升级增加的点数都会减少 Bi。

⌈AiBi⌉(上取整)次之后,再升级该技能将不会改变攻击力。现在小蓝可以总计升级 M 次技能,他可以任意选择升级的技能和次数。

请你计算小蓝最多可以提高多少点攻击力?

输入格式
输入第一行包含两个整数 N 和 M。
以下 N 行每行包含两个整数 Ai 和 Bi。

输出格式
输出一行包含一个整数表示答案。

数据范围
对于 40% 的评测用例,1≤N,M≤1000;
对于 60% 的评测用例,1≤N≤104,1≤M≤107;
对于所有评测用例,1≤N≤105,1≤M≤2×109,1≤Ai,Bi≤106。

输入样例:
3 6
10 5
9 2
8 1
输出样例:
47

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 1e5+10;

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

bool check(int mid)
{
    LL sum = 0;
    for(int i = 1; i <= n; ++i)
    {
        if(a[i] >= mid)
        {
            sum += (a[i] - mid) / b[i] + 1;
        }
    }
    return sum >= m;
}

int main()
{
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    
    cin >> n >> m;
    for(int i = 1; i <= n; ++i) cin >> a[i] >> b[i];
    
    int l = 0, r = 1e6;
    while(l < r)
    {
        int mid = (LL)l + r + 1 >> 1;
        if(check(mid)) l = mid;
        else r = mid - 1;
    }
    
    LL res = 0, cnt = 0;
    for(int i = 1; i <= n; ++i)
    {
        if(a[i] >= r)
        {
            int c = (a[i] - r) / b[i] + 1;
            int end = a[i] - (c - 1) * b[i];
            cnt += c;
            res += (LL)(a[i] + end) * c / 2;
        }
    }
    
    cout << res - (cnt - m) * r << endl;  // 可能会有多个r,数量超过了m
    return 0;
}

三、序列

标签:多路归并

思路:这是一个典型的从 m m m n n n 列中,每一行选一个数组,求选到的数字之和最小的 n n n 个。我们可以先两行两行的合并找到最小的 n n n 个,然后遍历 m − 1 m - 1 m1 次,这样最后的 n n n 个数字就是最小的了。然后两个合并,我们可以先把 a a a 数组排个序,然后把这 n 2 n ^ 2 n2 的数排成 n n n 组,如下图所示,由于 a a a 是排好序了,所以每组最小的就是最前边的那个,然后把这 n n n 个数加入到堆中,然后记下当前下标,然后再变成每组下一个数即可,这样下来的 n n n 个数就是最小的了。
在这里插入图片描述

题目描述:

给定 m 个序列,每个包含 n 个非负整数。

现在我们可以从每个序列中选择一个数字以形成具有 m 个整数的序列。

很明显,我们一共可以得到 nm 个这种序列,然后我们可以计算每个序列中的数字之和,并得到 nm 个值。

现在请你求出这些序列和之中最小的 n 个值。

输入格式
第一行输入一个整数 T,代表输入中包含测试用例的数量。

接下来输入 T 组测试用例。

对于每组测试用例,第一行输入两个整数 m 和 n。

接下在 m 行输入 m 个整数序列,数列中的整数均不超过 10000。

输出格式
对于每组测试用例,均以递增顺序输出最小的 n 个序列和,数值之间用空格隔开。

每组输出占一行。

数据范围
0<m≤1000,0<n≤2000
输入样例:
1
2 3
1 2 3
2 2 3
输出样例:
3 3 4

示例代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
typedef pair<int,int> PII;

const int N = 2010;

int T;
int m, n;
int a[N], b[N], c[N];

void merge()
{
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    for(int i = 0; i < n; ++i) heap.push({a[0] + b[i], 0});
    
    for(int i = 0; i < n; ++i)
    {
        auto t = heap.top(); heap.pop();
        int v = t.first, p = t.second;
        c[i] = v;
        heap.push({v - a[p] + a[p+1], p+1});
    }
    
    memcpy(a, c, sizeof a);
}

int main()
{
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    
    cin >> T;
    while(T--)
    {
        cin >> m >> n;
        for(int i = 0; i < n; ++i) cin >> a[i];
        
        sort(a, a+n);
        for(int i = 0; i < m - 1; ++i)
        {
            for(int j = 0; j < n; ++j) cin >> b[j];
            merge();
        }
        
        for(int i = 0; i < n; ++i) cout << a[i] << " ";
        cout << endl;
    }
    
    return 0;
}
  • 33
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lijiachang030718

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值