【算法题解】 超市(贪心/堆/并查集)

超市

题目

超市里有 N 件商品,每件商品都有利润 pi 和过期时间 di,每天只能卖一件商品,过期商品不能再卖。

求合理安排每天卖的商品的情况下,可以得到的最大收益是多少。

输入格式

输入包含多组测试用例。

每组测试用例,以输入整数 N 开始,接下来输入 N 对 pi 和 di,分别代表第 i 件商品的利润和过期时间。

在输入中,数据之间可以自由穿插任意个空格或空行,输入至文件结尾时终止输入,保证数据正确。

输出格式

对于每组产品,输出一个该组的最大收益值。

每个结果占一行。

数据范围

0≤N≤10000,
1≤pi,di≤10000
最多有 14 组测试样例

输入样例:
4  50 2  10 1   20 2   30 1

7  20 1   2 1   10 3  100 2   8 2
   5 20  50 10
输出样例:
80
185

失败的贪心尝试

  • 一个非常朴素的想法对日期有限排序,即过期时间早的先卖出,然后让天数自加.对于过期时间相同的商品优先卖利润高的

  • 一组不能通过的数据

    10 1
    20 2
    15 2
    5  3
    
    • 即优先卖掉的可能是价值低的,全局不一定最优
#include<bits/stdc++.h>
using namespace std;
const int N=10005;
int a[N],b[N];
int main(){
    int n;
    while(cin>>n){
        for(int i=0;i<n;i++)cin>>a[i]>>b[i];
        vector<int> idx(n);
        iota(idx.begin(),idx.end(),0);
        sort(idx.begin(),idx.end(),[&](int i,int j){
            if(a[i]!=a[j])return a[i]>a[j];
            return b[i]<b[j];
        });
        int cur=1,ans=0;
        for(auto i:idx){
            // cout<<'@'<<i<<endl;
            if(b[i]>=cur){ans+=a[i];
            cur++;
            }
            
        }
        cout<<ans<<endl;
    }
    return 0;
}

解法1. 堆+贪心

  • 建立一个小顶堆,堆中的元素表示在贪心决策的过程中前 t t t( t t t为堆的大小)天拟售出的最大利润商品集合
  • 按商品过期时间进行从小到大排序,每次将商品的过期时间 d d d与堆的大小 t t t进行比较
    • 如果 d > t d>t d>t则说明没有过期,加入堆中
    • 如果 d = = t d==t d==t则说明前 t t t天已经每天安排了商品,需要将当前元素的价值与堆顶元素(价值最低的元素)进行比较,如果价值比堆顶元素大,将其替换掉堆顶元素能产生更大价值.否则则不操作维持现状
    • 如果 d < t d<t d<t则说明前 t t t天已经安排了销售商品,不能在其过期前售出
#include <bits/stdc++.h>

// #define debug
using namespace std;
const int N = 10005;
int a[N], b[N];

int main() {
#ifdef debug
    freopen("in.txt", "r", stdin);
#endif
    ios::sync_with_stdio(0);
    int n;
    while (cin >> n) {
        priority_queue<int, vector<int>, greater<int>> pq;//优先队列
        for (int i = 0; i < n; i++)cin >> a[i] >> b[i];
        vector<int> idx(n);
        iota(idx.begin(), idx.end(), 0); //生成0~n-1的下标,并对下标进行排序
        sort(idx.begin(), idx.end(), [&](int i, int j) {//c++ 匿名函数
            return b[i] < b[j];
        });
        for (auto i: idx) {
            if (b[i] > pq.size())pq.push(a[i]);//过期时间还没到
            else if (b[i] == pq.size() && a[i] > pq.top()) {//刚好到过期时间并且比候选商品价值最小的要大
                pq.pop();
                pq.push(a[i]);
            }
        }
        int sum = 0;
        while (!pq.empty()) {
            // cout << pq.top() << endl;
            sum += pq.top();
            pq.pop();
        }
        cout << sum << endl;
    }
    return 0;
}

解法2. 并查集+贪心

  • 要想理解并查集的做法要先看未优化的 O ( n 2 ) O(n^2) O(n2)版本
  • 未优化的 O ( n 2 ) O(n^2) O(n2)版本
    • 首先对商品利润进行从大到小排序,然后将利润大的商品在其过期前尽可能晚一点的时刻售出
    • 简单的证明(交换论证法)
    • 假设不将利润最大的商品 A 1 A_1 A1放在其过期时刻之前的最晚时刻销售,该方案能产生一个最优解 O O O
    • 那么将其后面安排的销售商品提前,与 A 1 A_1 A1进行交换(因为这些商品在 O O O的安排情况是不过期的,提前销售必然也是不过期的),可以产生一个等价的最优解 O ′ O' O
    • 同理将利润次大的商品依次进行轮换,能产生一个最优解,满足在商品过期前尽可能晚一点的时刻售出的贪心策略
    • image-20221231214258577
//时间复杂度O(n^2),会超时
#include <bits/stdc++.h>

#define debug
using namespace std;
const int N = 10005;
int a[N], b[N];
bool schedule[N];

int main() {
#ifdef debug
    freopen("in.txt", "r", stdin);
#endif
    ios::sync_with_stdio(0);
    int n;
    while (cin >> n) {
        memset(schedule, 0, sizeof schedule);
        for (int i = 0; i < n; i++)cin >> a[i] >> b[i];
        vector<int> idx(n);
        iota(idx.begin(), idx.end(), 0); //生成0~n-1的下标,并对下标进行排序
        sort(idx.begin(), idx.end(), [&](int i, int j) {//c++ 匿名函数
            return a[i] > a[j]; //按商品利润从大到小排序
        });
        int sum = 0;
        for (auto i: idx) {
            int k = b[i];//过期日期
            while (k >= 0 && schedule[k])k--;//在商品过期前安排一个日期销售
            if (k >= 1) {
                schedule[k] = true;//安排在第k天销售
                sum += a[i];
//                cout << '@' << a[i] << endl;
            }
        }
        cout << sum << endl;
    }
    return 0;
}
  • 很不幸,未优化的算法时间复杂度是 O ( n 2 ) O(n^2) O(n2)的,会超时
  • 使用路径压缩的并查集进行优化
  • 并查集做法
    • 并查集的 p [ x ] p[x] p[x]根表示从 x x x天开始往前的最近的一个空闲位置,初始化时 p [ x ] = x p[x]=x p[x]=x,即第 x x x天过期的商品将在 x x x天售出
    • 排序依然是根据商品的价值从大到小排序
    • 当前一个决策的商品决定了在第 r r r天销售,那么会将其根 p [ r ] p[r] p[r]设置为 r − 1 r-1 r1,意味着当下一件决策商品也打算占用 r r r位置时,由于被鸠占鹊巢,将其放在前一个位置 r − 1 r-1 r1,如果依旧被占用直至决策位置到了0则说明这件商品不能被售出
    • image-20221231220318409
  • 时间复杂度
    • 排序 O ( n log ⁡ n ) O(n\log n) O(nlogn),贪心决策 O ( n ) O(n) O(n),中的时间复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

猜你喜欢

转载自blog.csdn.net/u011459717/article/details/128509148
今日推荐