多路归并-优先队列的应用

文章讨论了如何利用优先队列解决涉及k大/小值的算法问题,如鱼塘钓鱼、技能升级和序列操作。强调了在不同场景下的优化策略,以及处理大数据范围时的时间复杂度分析和优化技巧。
摘要由CSDN通过智能技术生成

这类算法大多都需要借助优先队列实现,用于求某种类型前 k k k 大/小的方案。

AcWing 1262. 鱼塘钓鱼

在这里插入图片描述
思路:使用优先队列帮助我们进行最优的选择,即每次应该去哪个地方进行钓鱼,但同时我们需要处理出中间路程所花费的时间,因为数据范围比较小,所以我们可以对所到达的最远的池塘进行遍历,然后运用优先队列进行维护即可。
同时解释一个问题,在一开始,我们便减去了路上所需要的时间,在后续即使出现了例如 a − b − c − b − a a-b-c-b-a abcba这种情况,我们也不需要进行考虑路上的消耗时间,因为我们计算的只是最终过程,即对于上述,我们需要在 a a a点进行两次, b b b点进行两次, c c c点进行一次,那么我们可以在 a a a点把两次鱼钓完了再去 b b b点,对于 b b b点同理。因此路上的消耗只需要考虑一遍即可。

#include <bits/stdc++.h>

using namespace std;
const int N = 5e5 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<int, 3> ar;
int mod = 1e9+7;
// const int maxv = 4e6 + 5;
// #define endl "\n"


void solve()
{
    int n;
    cin>>n;
    vector<int> a(n+5),b(n+5),s(n+5);
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) cin>>b[i];
    for(int i=2;i<=n;i++) {
        cin>>s[i];
        s[i]+=s[i-1];
    }
    int t;
    cin>>t;
    int ans=0;
    for(int i=1;i<=n;i++){
        int rt=t-s[i];
        priority_queue<pll> q;
        for(int j=1;j<=i;j++){
            q.push({a[j],j});
        }
        int res=0;
        while(!q.empty()&&rt>0){
            auto [ai,id]=q.top();
            q.pop();
            res+=ai;
            rt--;
            ai-=b[id];
            if(ai>0) q.push({ai,id});
        }
        ans=max(ans,res);
    }
    cout<<ans<<endl;
} 

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    t=1;
    cin>>t;
    while(t--){
        solve();
    }
    system("pause");
    return 0;
}

AcWing 4656. 技能升级

在这里插入图片描述
思路:对于较小的数据范围,我们可以通过优先队列去得到答案,即把最优结果从队首取出,然后把削减后的价值再次入队。但这题数据范围过大,正解为二分。

AcWing 1378. 谦虚数字

在这里插入图片描述
思路:对于这种求第k大的题目,我们可以使用优先队列,每次把队首弹出来,然让队首和序列的每个值相加后放入队列中,这样循环k次即为答案。
但是因为这题数据范围过大,具体的时间复杂度为 M N l o g m MNlog_m MNlogm,因此会有一个点超时,所以我们需要使用另一种方法。
我们可以知道,对于第 k k k 个数,其一定是由第 k − 1 k-1 k1 个数乘序列中的某个的来,所以我们可以三重循环去求出第 k k k 个数,第一层枚举现在是求第 i i i 大的数,第二层循环为枚举 1 − ( i − 1 ) 1-(i-1) 1(i1)中的数,第三层则是枚举原序列。由于肯定会超时,所以考虑优化,我们可以使用一个数组记录对于第二层的每一个数,其临界的合法状态,我当前的 f [ k ] f[k] f[k]肯定是由 f [ k − 1 ] f[k-1] f[k1]乘上某个数转移而来,那么我就使用数组记录每个数最大合法的 f [ i ] f[i] f[i],使得
a [ j ] × f [ i ] ≤ f [ k − 1 ] a[j]\times f[i]\leq f[k-1] a[j]×f[i]f[k1]
因此使用数组进行记录 i i i的值即可。

#include <bits/stdc++.h>

using namespace std;
const int N = 5e5 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<int, 3> ar;
int mod = 1e9+7;
// const int maxv = 4e6 + 5;
// #define endl "\n"


void solve()
{
    int n,k;
    cin>>n>>k;
    vector<int> a(n+5);
    for(int i=1;i<=n;i++) cin>>a[i];
    vector<int> ans(k+5);
    vector<int> st(k+5);
    ans[0]=1;
    for(int i=1;i<=k;i++){
        int res=2e9;
        for(int j=1;j<=n;j++){
            while(a[j]*ans[st[j]]<=ans[i-1]) st[j]++;
            res=min(res,a[j]*ans[st[j]]);
        }
        ans[i]=res;
    }
    cout<<ans[k]<<endl;
} 

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    t=1;
    // cin>>t;
    while(t--){
        solve();
    }
    system("pause");
    return 0;
}

暴力做法:

#include <bits/stdc++.h>

using namespace std;
const int N = 5e5 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<int, 3> ar;
int mod = 1e9+7;
// const int maxv = 4e6 + 5;
// #define endl "\n"


void solve()
{
    int n,k;
    cin>>n>>k;
    k--;
    vector<int> s(n);
    set<ll >se;
    for(int i=0;i<n;i++){
        int x;
        cin>>x;
        s[i]=x;
        se.insert(x);
    }
    for(int i=0;i<k;i++){
        auto t=*se.begin();
        se.erase(t);
        for(int j=0;j<n;j++) se.insert(s[j]*t);
    }
    cout<<*se.begin()<<endl;
} 

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    t=1;
    // cin>>t;
    while(t--){
        solve();
    }
    system("pause");
    return 0;
}

abc297 E - Kth Takoyaki Set

在这里插入图片描述
思路:和上一题一样,只不过把乘法换成了加法而已。又因为数据范围比较小,所以直接暴力跑出结果即可。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef pair<int,int> pll;
typedef long long ll;

void solve()
{
    int n,k;
    cin>>n>>k;
    vector<ll> a(n);
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    set<ll> z;
    for(int i=0;i<k;i++){
        ll res=*z.begin();
        z.erase(res);
        for(int j=0;j<n;j++){
            z.insert(a[j]+res);
        }
    }
    cout<<*z.begin()<<endl;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t=1;
    while(t--){
        solve();
    }
    system("pause");
    return 0;
}

AcWing 146. 序列

在这里插入图片描述
思路:觉得比较难的一道题目了,不知道为什么给的简单。
考虑只有两个数的情况,当我们对两个数组进行排序后,最小值一定是 a [ 1 ] + b [ 1 ] a[1]+b[1] a[1]+b[1];次小值一定为
m i n ( a [ 1 ] + b [ 2 ] , a [ 2 ] + b [ 1 ] ) min(a[1]+b[2],a[2]+b[1]) min(a[1]+b[2],a[2]+b[1]),由此推广到第 k k k小值,其一定为 m i n ( a [ k ] + b [ k + 1 ] , a [ k + 1 ] + b [ k ] ) min(a[k]+b[k+1],a[k+1]+b[k]) min(a[k]+b[k+1],a[k+1]+b[k])
而对于m个序列而言,我们可以将一,二序列算出的答案再和第三个序列进行计算,再和第四,五,等等以此类推,最终得到答案。

#include <bits/stdc++.h>

using namespace std;
const int N = 5e5 + 5;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef array<int, 3> ar;
int mod = 1e9+7;
// const int maxv = 4e6 + 5;
// #define endl "\n"

void solve()
{
    int m,n;
    cin>>m>>n;
    vector<vector<int> > a(m+5,vector<int> (n+5));
    vector<int> ans(n+5);
    for(int i=1;i<=m;i++){
        for(int j=1;j<=n;j++){
            cin>>a[i][j];
            if(i==1) ans[j]=a[i][j];
        }
    }
    sort(ans.begin()+1,ans.begin()+1+n);
    for(int i=2;i<=m;i++){//m个序列,总共计算m-1次
        priority_queue<pll,vector<pll> ,greater<pll>> q;
        auto b=a[i];
        for(int j=1;j<=n;j++) {
            q.push({ans[1]+b[j],1});
        }
        vector<int> c(n+5);
        for(int j=1;j<=n;j++){
            auto [s,id]=q.top();
            q.pop();
            c[j]=s;
            q.push({s-ans[id]+ans[id+1],id+1});
            //即把接下来可能合法的值放入队中即可,优先队列会自动把最小的放前面
        }
        ans=c;
    }
    for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
    cout<<endl;
} 

int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    t=1;
    cin>>t;
    while(t--){
        solve();
    }
    system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值