本次课程主要就是联系了一些贪心题,一下为今天的解题报告。
T1:lg P1090 合并果子
这道题是2004年的NOIp提高组的T2,相信大家都早就AC了吧,所以在这里就不贴出题面了。 那么我们应该怎么样来求出这个最小的体力消费呢?
经过简单的推理,显然我们只需要找到两堆最少的果子然后将它们合并起来,最终的结果就一定是最小的。(越早合并的果子堆在后面的合并过程中将会被重复使用更多次,所以要尽早合并少的果子堆)
想明白了怎么贪心,那么我们只需要维护一个优先队列就可以了,每一次操作的时候出队两个(待合并的果子),入队一个(合并后的果子),最后将剩下的最后一个元素输出即可。
代码如下:
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
int main(){
int n,x;
while (scanf("%d",&n) == 1 && n){
priority_queue <int, vector<int>, greater<int> > q;
for (int i=1;i<=n;i++){
scanf("%d",&x);
q.push(x);
}
int ans=0;
for (int i=1;i<n;i++){
int a=q.top();q.pop();
int b=q.top();q.pop();
ans+=a+b;
q.push(a+b);
}
printf("%d\n",ans);
}
return 0;
}
然后今天我在翻题解的时候(以前A的题),看见了一个适合初学者的AC方法,虽然慢了点,但是可以过。
如果没有学过优先队列,不会用咋么办?不需要担心,因为这只是一道黄题,所以肯定有更友好的方法。我们可以维护一个有序数组来代替这个优先队列。每一次去除数组最后的两个元素,再使用插入排序将这两个元素的和插入到这个数组里面。需要注意的是,千万不要为了追求更少的码量而不用插入排序去用algorithm的sort,这时候因为数组基本有序所以时间复杂度将会退化到O(n^2),而显然这个代码就会TLE。
这个代码实现比较简单,就不附在这里了。
T2: CF401C Team
题目大意:构造一个 01 序列,包含 n 个 0,m 个 1 要求不存在连续 2 个 0,或 3 个 1 1 <= n; m<=1000000;
我们可以对这个序列有两种种循环节的处理方式:[0,1,0,1]或者[1,1,0],那么根据现有的这两种01串的基本结构,我们显然就可以推断出当存在n>m+1
或者m>n*2+1
的时候是不可能构架出这样的序列的。然后我们就可以进行分类讨论。
第一种情况:n>m*2
:这个时候我们可以假设第一种的循环节出现了x次,然后根据n与m列出一个方程就可以解出x的值。需要注意的是,当n=m+1
的时候,需要特判输出[0,1],如果m是奇数的话再多输出一位1。
对于另一种情况,只需要n对的[1,1,0],如果m-k*2!=0
那么再补上剩下的1就行了。
其实总感觉这道题更像是一道模拟/递推的题目。
这道题的代码就不贴出来了,理解了真正的方法其实代码就是一道模拟。
T3:CF413C Joepardy!
题目大意:“Jeopardy!”的决赛将有n问题,每个问题都有对应的得分ai,其中有m个问题可以选择不得分,而将现有总得分翻倍。你可以安排关卡的通过顺序和策略,求最大得分。
那么这道题其实让我想起了智力大冲浪,但其实两道题还是有较大的区别的。(实话讲觉得智力大冲浪比这道题更值得上绿题。)这道题因为可以翻倍,翻倍的得分是基于我们已经得到的分数的,所以我们需要在翻倍之前就需要尽可能的多得分,然后用翻倍得分和这题本身的分数比较,从而决定是否执行翻倍操作。
那么怎么样方便的进行呢?本人使用的是改动一下快排的cmp函数,将不可以翻倍的题目排到前端,将可以翻倍的题目置于后端并且从大到小排序(使大的得分可以享受到翻倍的待遇),然后线性扫描处理一遍数据即可。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
int dou;
long long pt;
} a[10000];
bool cmp(node x,node y){
if ((x.dou==1)&&(y.dou==1))
return (x.pt>y.pt);
else if ((x.dou==0)&&(y.dou==0))
return (x.pt>y.pt);
else if (x.dou==0) return true;
else return false;
}
int main(){
int n,m;
cin >> n >> m;
for (int i=1;i<=n;i++){
cin >> a[i].pt;
a[i].dou=0;
}
for (int i=1;i<=m;i++){
int x;
cin >> x;
a[x].dou=1;
}
sort(a+1,a+1+n,cmp);
long long ans=0;
for (int i=1;i<=(n-m);i++){
ans+=a[i].pt;
}
for (int i=(n-m+1);i<=n;i++){
if (a[i].pt>ans) ans+=a[i].pt;
else ans*=2;
}
cout << ans << endl;
return 0;
}
T4: CF546B Soldier and Badges
题目大意:给 n 个数,每次操作可以将一个数 +1,要使这 n 个数都不相同, 求最少要加多少? 1<= n <=3000
这道题如果我没有记错的话应该是曾经入选过某市小学组复赛试题的。显然这道题只能加不能减,所以我们只需要把数据排序之后,把重复的数据补到下一个有空档的位置。这个过程可以使用桶来实现,也可以直接排序之后对重复元素进行处理。
代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[10000];
int main(){
int n;
cin >> n;
for (int i=1;i<=n;i++){
cin >> a[i];
}
sort(a+1,a+1+n);
int s=0;
for (int i=2;i<=n;i++){
if (a[i]<=a[i-1]){
s+=(a[i-1]+1-a[i]);
a[i]=a[i-1]+1;
}
}
cout << s << endl;
return 0;
}
T5: CF492C Vanya and Exams
题目大意:有个有n门课程,每门课程他最多获得r学分,他只要所有课程的平均学分大于等于avg,他就可以获得奖学每门课程,他已经获得了ai学分,剩下的每个学分,都需要写bi篇论才能得到,然后问你,最少要写多少论文才能 获得奖学金 1<=n<=105;1<=avg<=106;1<=r<=10^9
这道题的最优化策略简直不要太简单,很明显为了写尽可能少的论文,我们要选择少的论文得一分的科目。所以只需要对第二个数据进行从小到大排序处理,然后从头一个一个加上去就可以了。需要注意的是本题的数据范围,建议打开long long不然会溢出。
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
struct node{
long long num,pt;
}a[100010];
bool cmp(node x,node y){
return x.num<y.num;
}
int main(){
int n,r;
long long ave;
cin >> n >> r >> ave;
ave*=n;
long long tot=0;
for (int i=1;i<=n;i++){
int x,y;
cin >> x >> y;
tot+=x;
x=r-x;
a[i].num=y;
a[i].pt=x;
}
ave-=tot;
sort(a+1,a+1+n,cmp);
long long ans=0;
if (ave<=0){
cout << "0" << endl;
return 0;
}
int i=1;
while (ave>0){
if (ave-a[i].pt>=0){
ans+=a[i].pt*a[i].num;
}else ans+=ave*a[i].num;
ave-=a[i].pt;
i++;
}
cout << ans << endl;
return 0;
}
T6: lgP1717钓鱼
题目描述
话说发源于小朋友精心设计的游戏被电脑组的童鞋们藐杀之后非常不爽,为了表示安慰和鼓励,VIP999 决定请他吃一次“年年大丰收”,为了表示诚意,他还决定亲自去钓鱼。
但是,因为还要准备 NOIP2013, z老师只给了他 H 个小时的空余时间,假设有 n 个鱼塘都在一条水平路边,从左边到右编号为 1, 2, 3 … n 。
VIP是个很讲究效率的孩子,他希望用这些时间钓到尽量多的鱼。他从湖1出发,向右走,有选择的在一些湖边停留一定的时间钓鱼,最后在某一个湖边结束钓鱼。他测出从第 i 个湖到 i+1个湖需要走 5×ti 分钟的路,还测出在第 i个湖边停留,第一个5分钟可以钓到鱼 f_i,以后再每钓5分钟鱼,鱼量减少 d_i。为了简化问题,他假定没有其他人钓鱼,也不会有其他因素影响他钓到期望数量的鱼。请编程求出能钓最多鱼的数量。
首先我们来假设一下,如果钓鱼人可以瞬间移动,那么我们可以发现,我们要去选择一些单位时间内钓鱼数量最多的鱼塘。那么再加上(非常显然的)不能走回头路的原则,所以我们可以发现钓鱼的顺序其实可以是由模拟出来的几个决策累计起来的,而不用一遍一遍来回跑。解决了这个问题,我们就只需要维护一个钓鱼数量的队列就行了。
type fish=record
a,b,c,d:longint;
end;
var
f:array[0..1100]of fish;
n,h,p,maxx,i,j,k,sum,ans:longint;
function max(x,y:longint):longint;
begin
if x>y then exit(x)
else exit(y);
end;
begin
readln(n);
readln(h);
h:=h*12;
for i:=1 to n do
read(f[i].a);
for i:=1 to n do
read(f[i].b);
for i:=1 to n-1 do
read(f[i].c);
for k:=1 to n do
begin
for i:=1 to k do
f[i].d:=f[i].a;
h:=h-f[k-1].c;
p:=h;
sum:=0;
while (p>0) do
begin
dec(p);
maxx:=0;
for i:=1 to k do
if maxx<f[i].d then
begin
j:=i;
maxx:=f[i].d;
end;
inc(sum,maxx);
f[j].d:=f[j].d-f[j].b;
end;
ans:=max(ans,sum);
end;
writeln(ans);
end.
(欢乐的P党咸鱼)
T7 P2434 区间
现给定n个闭区间[ai, bi],1<=i<=n。这些区间的并可以表示为一些不相交的闭区间的并。你的任务就是在这些表示方式中找出包含最少区间的方案。你的输出应该按照区间的升序排列。这里如果说两个区间[a, b]和[c, d]是按照升序排列的,那么我们有a<=b<c<=d。
请写一个程序:
读入这些区间;
计算满足给定条件的不相交闭区间;
把这些区间按照升序输出。
那么这个题目说实话我并不是用贪心做的。因为是一个最大值只有1000000的区间,所以咸鱼的我又偷懒写了一个差分约束。为了避免两个区间相互抵消,我就用了两个数组。用一个变量,如果从0加到正数那就是一段区间的开始,如果从正数减到0那就是一段区间的结束。所以,这道题就没什么的了吧,码量很短,就懒得贴了。
T8:lgP2920 Time Management
作为一名忙碌的商人,约翰知道必须高效地安排他的时间.他有N工作要 做,比如给奶牛挤奶,清洗牛棚,修理栅栏之类的.
为了高效,列出了所有工作的清单.第i分工作需要T_i单位的时间来完成,而 且必须在S_i或之前完成.现在是0时刻.约翰做一份工作必须直到做完才能停 止.
所有的商人都喜欢睡懒觉.请帮约翰计算他最迟什么时候开始工作,可以让所有工作按时完成.(如果无法完成全部任务,输出-1)
这道题目也和智力大冲浪相类似,不过只需要计算开始工作的最晚时间就行了。那么其实我们就可以强行模拟这道题,严格意义上说也不算贪心吧。直接把数据的deadline从大到小排序,然后把所有任务密铺在整个时间轴上,如果最终时间轴的起点<1的话,那么就可以输出-1,不然直接输出时间轴的起点就可以了。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct node{
int dl,ti;
} a[1005];
bool cmp(node x,node y){
return x.dl>y.dl;
}
int main(){
int n;
cin >> n;
for (int i=1;i<=n;i++){
cin >> a[i].ti >> a[i].dl;
}
sort(a+1,a+1+n,cmp);
int ans=a[1].dl;
for (int i=1;i<=n;i++){
if (ans<=a[i].dl) ans-=a[i].ti;
else ans=a[i].dl-a[i].ti;
}
if (ans<0) cout << "-1" << endl;
else cout << ans << endl;
return 0;
}
T9:BZOJ3544
给定一段序列,选取它的一段连续子序列使序列内所有数的和对mod取模的值最大。求这个最大值。
那么第一眼看到这个题目的话我是想直接线性扫描的,但是因为结果是一个循环的导致不能判断什么时候要出队就妥妥的WA了,后面,我乖乖地点开了baidu,搜了一下题解……(划掉)
这道题如果要保证这个值最大,需要维护一个模mod意义下的前缀和,然后逐步将前缀和加入到一个set中,每加入一个元素之前,我们都要先寻找比这个元素大的元素,计算出答案并更新。注意首先应特判前缀和数组中的每一个值是否有可能成为答案。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<set>
using namespace std;
long long s,mod,a,ans,p;
set<long long> S;
int main(){
int n;
cin >> n;
cin >> mod;
for (int i=1;i<=n;i++){
scanf("%lld",&a);
if (a<0) a+=(abs(a/mod)+1)*mod;
s+=a,s%=mod;
ans=max(ans,s);
if (S.upper_bound(s)!=S.end()){
p=*(S.upper_bound(s));
ans=max(ans,(s+mod-p)%mod);
}
S.insert(s);
}
cout << ans << endl;
system("pause");
return 0;
}
STL大法吼啊!!!