CCPC2019网络赛. Fishing Master

  • 题意:给出n条鱼需要炖的时间ti和去抓任意一条鱼的时间k(每次只能抓一条鱼和炖一条鱼),并且规定在抓鱼中途不允许放鱼进锅里炖,可以将鱼放去炖后(在鱼正在被炖的时候)去抓鱼,问将所有鱼炖好后所需要的最少时间。
  • 思路:贪心 + 优先队列
  • 解析:这道题必须花费的时间其实是:抓一条鱼的时间 + 炖所有鱼的时间。但是前提是什么呢?前提是炖这条鱼的时候剩下的(n-1)条鱼可以在这个时间内全部抓上来,这显然并不是所有数据都会成立的,那么我们肯定希望这个锅尽可能的一直被用来炖鱼,并且能在煮这条鱼的时候能打捞上更多的鱼。
    • 首先我们需要煮每条鱼的时间从大到小排序,煮第i条鱼时可以并行打捞上ti/k条鱼,打捞完毕后,肯定还要额外花ti % k(可能为0)的时间去煮鱼,这时候我们有两种选择:
      • 第一种:等ti % k的时间,放下一条鱼进去煮
      • 第二种:让锅空出k - ti % k的时间去抓一条鱼
      • 这很显然,假如我后面有鱼等着被煮,我肯定是等上一条鱼煮完赶紧丢这条进去煮,再通过这条鱼被煮的时候并行去捞鱼不香吗?我不可能是让锅空出来去抓一条鱼回来再煮吧,除非我后面没鱼煮,那么我必须抓一条鱼回来,那么这种情况本来就不希望发生的,一直有鱼被等着煮多好呢,所以这时候我们贪心的选择之前ti % k(额外煮鱼时间)最大的鱼,在它并行捞完ti / k条鱼后再多捞一条这样可以使得总的(k - ti % k)最小。
  • 代码:
#include<iostream>
#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
int T, n;
ll k, t[N];
priority_queue<ll> q; //存放选择多抓一条鱼,锅空闲出来浪费的时间
bool cmp(ll x, ll y)
{
    return x > y;
}
int main()
{
    scanf("%d", &T);
    while(T --)
    {
        scanf("%d%lld", &n, &k);
        for(int i = 1; i <= n; i++) scanf("%lld", &t[i]);
        sort(t + 1, t + 1 + n, cmp); //将煮鱼时间从大到小进行排序
        ll res = k; //第一条鱼必须要先去抓上来
        ll pos = 1; //当前已经打捞上来鱼的数量
        for(int i = 1; i <= n; i++)
        {
            if(pos < i) //该条鱼没被打捞上来
            {
                res += k - q.top(); //选择之前剩余最多时间的鱼的间隙去把这条鱼打捞上来
                q.pop();
            }
            res += t[i]; //加上煮每条鱼的时间(必)
            q.push(t[i] % k); //第i条在煮时候打捞完毕t[i]/k条鱼还剩余的时间
            pos += t[i] / k; //在第i条鱼被煮时能并行打捞上来鱼的数量
        }
        while(q.size()) q.pop(); //清空队列
        printf("%lld\n", res);
    }
    return 0;
}

CCPC2018网络赛. Buy and Resell

  • 题意:有n座城市编号为 1 ~ n,按顺序访问每座城市,到达每座城市后可以有三种选择:卖出手中的一件物品(前提有物品)获得a[i]元、购买一件物品花费a[i]元、不买也不卖。可以将所有物品看作一种物品,只是在不同地方的价值不一样罢了,问能达到的最大收益为多少,并且在最大收益下要交易次数(售出、买入各算一次)尽可能的少,需要输出最大收益和最少的交易次数。
  • 思路:反悔贪心 + 优先队列
  • 解析:此题的贪心策略是买入更小价格的物品,以更高的价格卖出去,某博主给出了一个推导公式:
    Cbuy 为全局最优解中买入某物品的价格, Csell 为全局最优解中卖出某物品的价格, Ci为任意一天物品的价格

 

\[C_{sell}-C_{buy}=\left(C_{sell}-C_i\right)+\left(C_i-C_{buy}\right) \]

 

  • 比如一个例子:1 10 100,在第一个城市买了一个物品花费1元,在第二个城市卖出时可得10元,收益为9元,那么此时可以认为此时不存在价值为1元的物品,而存在两个价值为10元的物品,因为到第三个城市我们可以发现100 -10 = 90,加上之前的收益一共为99,那么为最大收益,相当于有一种传递性,那这时候可以认为只存在10元价值的物品和100元价值的物品。然后交易次数用一个map记录所有值的操作次数即可。
    可能看代码注释更好理解~
  • 代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 5;
int n, T;
ll a[N];
priority_queue<ll, vector<ll>, greater<ll> > q;
int main()
{
    scanf("%d", &T);
    while(T --)
    {
        map<ll, ll> mp; //数值为x曾经买/卖的次数
        mp.clear();
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
        ll res = 0, cnt = 0;
        for(int i = 1; i <= n; i++)
        {
            q.push(a[i]); //让其入队,寻找高价的时候卖出
            ll x = q.top();
            if(a[i] > x) //进行买卖
            {
                res += a[i] - x; //将中间计算的最优解的部分值求和
                if(mp[x]) mp[x] --; //如果之前该值进行过买卖操作, 则此时a[i]替换x,传递它的值
                else cnt += 2; //x之前没有发生过交易, 说明x首次交易,则次数加2(x买, a[i]卖)
                q.pop(); //让当前队列最小值出队(让a[i]成为x的中间变量去寻找更大的卖出价格)
                q.push(a[i]); //二次放入a[i],可以使其有反悔机会:不卖(中间变量)
                mp[a[i]] ++; //a[i]在队列的个数增加
            }
        }
        while(q.size()) q.pop(); //队列清空
        printf("%lld %lld\n", res, cnt);
    }
    return 0;
}