首页 > 技术文章 > 【题解】 CF767E Change-free

EiffelA-blog 2020-10-05 09:58 原文

洛谷链接

这个题翻译忘了输入,我看的英语原文......

首先,这是一道贪心题

我的大致方法:pair+堆优

题目分析:

从第一天开始,到最后一天,每天可以选择找钱或者不找钱.

如果不找钱,则零钱数m减去多出的零钱;

如果找钱,则食堂大爷的怒气值上升找钱数乘每天心情值w。(下文称ann值)

贪心策略:

既然这样,我们就可以利用贪心思想

有两种情况:

情况一:

如果手中的零钱数是足够的,就选择用手中的零钱支付所需要的零钱;

情况二:

如果手中的零钱数不够,就找出之前用零钱支付多余钱数的一天,让时间回溯,在那一天选择用100元支付零钱,让大爷找钱。

这样就多出了100元的零钱,足以支付当前所需支付的零钱,但收银员的怒气值会上升,需要专门记录下来(只不过大爷更生气了而已......)

 

Q: 选哪一天呢?

A:还用想吗?当然是使大爷怒气值上升最小的那一天,也就是在之前经过的天数中选择ann值的最小值。

 

贪心正确证明:

 

反证法:

 

如果在需要零钱时(也就是手中零钱不足以支付多余钱数),选择非最小的ann值,那我们将在可以支付当前多余钱数的情况下,收银员的怒气值上升比最小ann值所上升的怒气值要高,不符合最优方案,故错误。

 

(同样结果却使收银员怒气值更高,非最优方案)

 

结论:

 

不能选用非最小ann值

 

结论成立,贪心方案正确

优化:

 

我想到这里,噼里啪啦地打完了代码,满怀信心地提交

结果:

 

我:??????

我:!!!!!!

好吧,看来得优化

我冥思苦想(这次没看题解),想到既然是求动态最小值,那用堆优找ann值

可以用pair类型把之前经过的天的ann值和第几天存下

 

于是我打上去一个堆优,结果样例二又过不去了

我就这样改了一遍又一遍,总算样例都过了~

提交上去,果然看到了一片绿~~

注意:

1、记得开long long

 

(m的最大值为一的九次方,若叠加几次可能会爆int(也可能不会爆)

我不会算

2、用100元能够正好支付时不加入堆中

题目中不允许多给钱,如果用100元正好支付完就无法来换零钱,故不加入堆中

3、若用pair类型存储数据,要将ann值放在pair中的first

pair类型排序时默认用first来排序

PS:

堆中存的是可能需要让收银员找钱的天数

一开始经过的天数(没有让收银员找零)都可能是需要让收银员找钱的天数,故放进堆中

若出现情况二,就利用堆找到ann最小值,完成找钱操作并使已找过钱的天数被pop,避免题目中多给钱的情况

 

完整AC代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#define MAXN 100001
#define LL long long
#define M(i, j) make_pair(i, j)
using namespace std;

LL c[MAXN], w[MAXN], zheng[MAXN], ling[MAXN];//zheng[i]是第i天100元花费的个数,ling[i]是第i天零钱花费的元数
LL n, m, h, k, ans=0;
int vis[MAXN];
priority_queue<pair<LL, LL>, vector< pair<LL, LL> >, greater< pair<LL, LL> > > q;//小头堆(STL万岁!)

int main(){

  scanf("%lld %lld", &n, &m);
  for (int i = 1; i <= n; i++){
  scanf("%lld", &c[i]);
  zheng[i] = c[i] / 100;//算出每天至少花费多少100元
  c[i] %= 100;//预处理,c[i]只存需花费零钱
  }
  for (int i = 1; i <= n; i++) scanf("%lld", &w[i]);
  for (int i = 1; i <= n; i++){
    ling[i] += c[i];//题目中不允许多给钱,如果用100元正好支付完就无法来换零钱
    if (c[i]) q.push(M(w[i]*(100 - c[i]), i));//用pair类型将天数与大爷可能上升的怒气值存进堆里(pair类型默认用first来排序)
    if (c[i] > m){
      h = q.top().first;//取出最小怒气值
      k = q.top().second;//取出是哪一天
      q.pop();
      vis[k] = 1;//标记这一天已经换了零钱了(这句好像没用)
      ans += h;//积累怒气值
      ling[k] -= c[k];//那一天不用找零了
      zheng[k]++;//那一天100元整数+1
      m += 100;//多出100元零钱
    }
    m -= c[i];
  }
  printf("%lld\n", ans);
  for (int i = 1; i <= n; i++){
    printf("%lld %lld\n", zheng[i], ling[i]);
  }
  return 0;
}

 

希望能帮到您~~⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄.

 

推荐阅读