写在前面:
本系列博客仅作为本人十一假期过于无聊的产物,对小学期的程序设计作业进行一个总结式的回顾,如果将来有BIT的学弟学妹们在百度搜思路时翻到了这一条博客,也希望它能对你产生一点帮助(当然,依经验来看,每年的题目也会有些许的不同,所以不能保证每一题都覆盖到,还请见谅)。
不过本人由于学艺不精,代码定有许多不足之处,欢迎各位一同来探讨。
同时请未来浏览这条博客的学弟学妹们注意,对于我给出完整代码的这些题,仅作帮助大家理解思路所用(当然,因为懒,所以大部分题我都只给一个伪代码)。Anyway,请勿直接复制黏贴代码,小学期的作业也是要查重的,一旦被查到代码重复会严厉扣分,最好的方法是浏览一遍代码并且掌握相关的要领后自己手打一遍,同时也要做好总结和回顾的工作,这样才能高效地提升自己的代码水平。
加油!
成绩 | 10 | 开启时间 | 2021年08月31日 星期二 12:30 |
折扣 | 0.8 | 折扣时间 | 2021年09月7日 星期二 23:00 |
允许迟交 | 否 | 关闭时间 | 2021年10月10日 星期日 23:00 |
Description
小张最近沉迷上一款手机游戏北理工的恶龙。在这个游戏中你通过提升攻击力击败恶龙,打败所有恶龙后你可以获得游戏的胜利。
在这款游戏中,每一条恶龙有一个难度值和一个经验值。游戏中的英雄具有攻击力。游戏一开始英雄的攻击力。打到一条恶龙你的攻击力需要大于等于难度值。在你击败恶龙以后,你的攻击力会增加经验值。
当然,你有时需要额外提升你的攻击力,此时你只需氪金1元,就能增长一点攻击力。小张想知道,如果他自己决定挑战恶龙的顺序,要想击败所有恶龙至少需要氪金多少钱?
Input
第一行一个数。
接下来行每行两个数。
Output
一个整数,表示小张最少需要氪金多少钱。
Notes
直接打败第一条恶龙,此时,花费0元。
直接打败第二条恶龙,此时,花费0元。
氪金3元,此时,打败第三条恶龙,此时。
最后直接打败第四条恶龙。
测试用例 1 | 以文本方式显示
| 以文本方式显示
| 1秒 | 64M |
题意分析:
有趣的贪心题。还记得之前我们怎么证明贪心策略的吗?今天他又来了。
这个题意应该不难懂,而且这个2e5的数据规模也基本断绝了你暴力搜索的念想,所以这题一定是一个P问题,那么我们就想想如何能“贪到底”吧。
你要是游戏打得多啊,这个肌肉记忆就应该被唤醒了——对面上单太肥了,你现在打不过,那就先去欺负对面AD嘛,多提提款,自己就有钱了,那再欺负对面的上单不就水到渠成了!
这题也是一样的思路,假如全都是那些击杀后能增长我们攻击力的“好龙”,我们肯定是要从攻击力低的打起(还是一样,读者先试证这个贪心策略,我会在稍后给出证明)。那么现在就有两个问题,一是要不要先打“好龙”再打“坏龙”,还是混合双打?二是如果全都是“坏龙”,我们又要如何处理?
如果你想自己寻找接下来的贪心策略,请在这里止步(当然,点进这个帖子的,估计已经挣扎这一步之后放弃了)。
如果你不能自己找到贪心策略的话,我这里给出贪心策略,请读者试着自己先证明一下(如果仍然证明不了的话,我会把具体的证明过程放在下面供大家参考):先打正收益的“好龙”、再打负收益的“坏龙”;在“好龙”中,先打难度低的“小怪”,再打难度高的“BOSS”;在“坏龙”中,先打难度+收益(此处收益为负)更高的“人畜无害龙”,再打难度+收益更低的“坏事做尽龙”。
贪心策略的证明:
首先,我们把龙分为好龙和坏龙之后,一定要先打好龙再打坏龙,为什么呢?因为先打好龙是没有坏处的:假如我们在打某一条好龙之前先打坏龙,那么打完坏龙我们的攻击力只会降不会增,如果降完之后还是打得过好龙,那我先打好龙也没有影响;如果降之前就已经打不过好龙,需要氪金了,那降了之后就需要氪更多金了;如果降之前打得过、降之后打不过了,那就更糟糕。总而言之言而总之,先打坏龙不会有任何好处,换句话说“与其先打坏龙自废武功,不如先打好龙攒点家底”(这句话是不是很熟悉)。
于是我们现在把所有龙分开来了,先打好龙。那么好龙的顺序又要如何排呢?其实也很简单,先打好打的,再打难打的。假如我在某一条相对好打的之前先打了另一条难打的,有以下几种可能:要么为了打难打的龙氪了很多金,之后的攻击力已经远远溢出了打好打的龙的需要,这就属于浪费;要么本身就已经能够干碎难打的龙了,那我先打好打的也没区别。总而言之言而总之,先打好打的龙肯定没错,换句话说“与其先挑战BOSS浪费钱,不如先打点小怪攒点家底”。
最难的一部分来了,坏龙我们要怎么处理呢?先打难的?还是先打损失小的?还是综合考虑两个因素?
我们逐个来排除吧,如果先打难的会有什么后果?给一个极端情况,某一条龙难度为100,收益为-1e1000,而其他坏龙难度不如它,但也不会对你的攻击力有如此致命的打击,很显然,这种情况下这样的龙是要作为“收尾”最后打的,一旦打完它,就要做好从此隐退江湖不再出招的觉悟。于是,先打难的,并不合理。
先打损失小的呢?假如有一条龙,难度10,收益-1,另一条龙难度100,收益-20,我们先打谁好呢?假如我们初始攻击力为0,先打前者再打后者,总共需要氪金10+91=101,如果先打后者再打前者需要氪金100,显然是先打后者更好。于是,先打损失小的,也不合理?
捏麻麻的,怎么打都不行了是吧。别急,我们先回到最初的起点。
什么叫氪金最少?考虑到我们要击败所有的龙,而所有的龙的总收益是一个定值,于是我们在打完所有的龙之后,剩余的攻击力就等于总收益+氪金量,既然总收益是定值,那么我们肯定希望我们打完所有的龙之后,剩余的攻击力越少越好,最好是打完最后一条龙就自废武功、金盆洗手、退隐江湖。
但是我们之前也提到了,一味地把损失大的留到后面打也不合理,因为可能会有某些龙的难度非常之低,能够支持我们打完这些损失大的龙之后依然还能“白嫖”的。对,我们的关键词就是“白嫖”,我们希望我们打完某些大BOSS之后能尽可能地多“白嫖”一些,这样我们一次氪金、永久收益,岂不美哉?
那么什么样的BOSS龙能够满足这样的条件呢?我们假定我们的攻击力为0,某条BOSS的难度为,打完对攻击力的损耗为,那么我们氪完金、打完龙之后剩余的攻击力就是,如果这个非常的大,那我们就更有机会白嫖其他小坏龙。当然,这个推理过程依然不能作为贪心策略的证明,我们还需要详细证明一下。假如我们先打这些低难度的小龙,再去挑战这个高难度、低损耗的BOSS会怎么样呢?我们首先要氪金打小龙、然后要氪金把小龙损耗的攻击力补回来、然后还要氪金打大龙。不难发现,三个氪金过程中,第一和第三个的总和和我们直接打大龙氪的金是一样的,而第二个氪金过程则是完完全全彻彻底底的浪费——能白嫖的,为什么要我氪?
(后来补充了另一个证明的思路,我们可以把难度值小于的龙和这条BOSS合并为一条巨型BOSS,这条BOSS的损耗最大,更有利于满足我们“剩余的攻击力越少越好”的这个目标,这也是一种证明思路)
于是,对于坏龙,我们证成了一种贪心策略,即用坏龙的难度减去打完之后的损耗,这个越大,我们就需要越先打,这样才能尽可能多的白嫖。
伪代码:
贪心策略证完,这道题也没有难度了。
读入数据,先分组,分为打完涨攻击力的“好龙”和降攻击力的“坏龙”;//思考一下,收益为0的放在哪里?
初始化一个变量作为总氪金量;
先对好龙按照难度值进行排序,从小到大依次打过去,过程中更新;
再对大龙按照难度值-损耗进行排序,从大到小依次打过去,过程中更新;
输出;
贴代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f;
struct cmp_p{ //对好龙进行排序
bool operator()(pair<int,int> a, pair<int,int> b){
if(a.first < b.first)
return true;
else return false;
}
};
struct cmp_n{ //对坏龙进行排序
bool operator()(pair<int,int> a, pair<int,int> b){
if(a.first + a.second > b.first + b.second)
return true;
else return false;
}
};
int main(){
//ifstream infile("input.txt", ios::in);
//ofstream outfile("output.txt", ios::out);
vector<pair<int,int>> positive;
vector<pair<int,int>> negative;
pair<int,int> temp;
int n;
cin >> n;
for(int i = 0; i < n; i++){
cin >> temp.first >> temp.second;
if(temp.second >= 0)
positive.push_back(temp);
else
negative.push_back(temp);
}
sort(positive.begin(), positive.end(), cmp_p());
sort(negative.begin(), negative.end(), cmp_n());
ll cur = 0;
ll input = 0;
for(int i = 0; i < positive.size(); i++){
if(cur < positive[i].first){
int dif = positive[i].first - cur;
cur += dif;
input += dif;
}
cur += positive[i].second;
}
for(int i = 0; i < negative.size(); i++){
if(cur < negative[i].first){
int dif = negative[i].first - cur;
cur += dif;
input += dif;
}
cur += negative[i].second;
}
cout << input << endl;
return 0;
}