洛谷P1909
题目描述
P 老师需要去商店买 n 支铅笔作为小朋友们参加 NOIP 的礼物。她发现商店一共有 3 种包装的铅笔,不同包装内的铅笔数量有可能不同,价格也有可能不同。为了公平起见,P 老师决定只买同一种包装的铅笔。
商店不允许将铅笔的包装拆开,因此 P 老师可能需要购买超过 n 支铅笔才够给小朋友们发礼物。
现在 P 老师想知道,在商店每种包装的数量都足够的情况下,要买够至少 n 支铅笔最少需要花费多少钱。
输入格式
第一行包含一个正整数 n ,表示需要的铅笔数量。
接下来三行,每行用2个正整数描述一种包装的铅笔:其中第 1 个整数表示这种包装内铅笔的数量,第 2 个整数表示这种包装的价格。
保证所有的 7 个数都是不超过 10000 的正整数。
输出格式
1 个整数,表示 P 老师最少需要花费的钱。
解题过程
思路:因为有三种铅笔都有,每种都有四个量,用四个数组存放四组量:dan每盒的铅笔数量,p每盒铅笔价钱,he至少购买的盒数,sum总价
第一次写出来的:
#include<iostream>
using namespace std;
int main(){
int n;
cin>>n;
int dan[4],p[4],he[4],sum[4];
for(int i=1;i<=3;i++){
cin>>dan[i]>>p[i];
he[i]=n/dan[i];
if((n%dan[i])!=0){
he[i]++;
}
sum[i]=he[i]*p[i];
}
int min=sum[1];
for(int i=2;i<=3;i++){
min=min<sum[i]?min:sum[i];
}
cout<<min;
}
第一次提交犯了个小错误:定义数组时[ ]内的是数组长度。调用数组时[ ]内是数组索引,如长度为4时,最大索引只有到3。
终于AC后,思考是否可以用某些函数简化一些操作,在题解中发现了ceil函数,也就是向上取整,正好可以优化我的代码中判断整盒购买是否整除的部分。向上取整ceil和向下取整floor都在库<cmath>中,记得将运算结果换为浮点,这两个函数才起作用ovo
第二次写出来的:
#include<iostream>
#include<cmath>
using namespace std;
int main(){
int n;
cin>>n;
int dan[4],p[4],he[4],sum[4];
for(int i=1;i<=3;i++){
cin>>dan[i]>>p[i];
he[i]=ceil(n*1.0/dan[i]);
sum[i]=he[i]*p[i];
}
int min=sum[1];
for(int i=2;i<=3;i++){
min=min<sum[i]?min:sum[i];
}
cout<<min;
}
好吧,我承认这是很简单的一道题,但是总是很担心自己的基础,也确实在写这些题中犯了很多小错误,就当查缺补漏的练习吧awa
洛谷P1089
题目描述
津津的零花钱一直都是自己管理。每个月的月初妈妈给津津 300 元钱,津津会预算这个月的花销,并且总能做到实际花销和预算的相同。
为了让津津学习如何储蓄,妈妈提出,津津可以随时把整百的钱存在她那里,到了年末她会加上 20% 还给津津。因此津津制定了一个储蓄计划:每个月的月初,在得到妈妈给的零花钱后,如果她预计到这个月的月末手中还会有多于 100 元或恰好 100 元,她就会把整百的钱存在妈妈那里,剩余的钱留在自己手中。
例如 11 月初津津手中还有 83 元,妈妈给了津津 300 元。津津预计11月的花销是 180 元,那么她就会在妈妈那里存 200 元,自己留下 183 元。到了 11 月月末,津津手中会剩下 3 元钱。
津津发现这个储蓄计划的主要风险是,存在妈妈那里的钱在年末之前不能取出。有可能在某个月的月初,津津手中的钱加上这个月妈妈给的钱,不够这个月的原定预算。如果出现这种情况,津津将不得不在这个月省吃俭用,压缩预算。
现在请你根据 2004 年 11 月到 12 月每个月津津的预算,判断会不会出现这种情况。如果不会,计算到 2004 年年末,妈妈将津津平常存的钱加上 20% 还给津津之后,津津手中会有多少钱。
输入格式
12 行数据,每行包含一个小于 350 的非负整数,分别表示 11 月到 12 月津津的预算。
输出格式
一个数字。如果不会不高兴则输出 0,如果会则输出最不高兴的是周几(用 1,2,3,4,5,6,7 分别表示周一,周二,周三,周四,周五,周六,周日)。如果有两天或两天以上不高兴的程度相当,则输出时间最靠前的一天。
解题过程
又是查缺补漏的一道简单题,又不出意料的犯了低级语法错误o(╥﹏╥)o
思路:三个vector数组:out每月预算支出,shou每月手中的钱,cun存的钱。out由输入读取,shou初始化为300,因为每个月都会得到300元,对这两个数组的同一索引(即同一月份)下的数据进行巴拉巴拉的加减乘除后,百元大钞加到cun里,剩的零钱加到下一个月(shou的下一索引)。
没什么好的想法了,故最终版本为:
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> out(15,0);
vector<int> shou(15,300);
vector<int> cun(15,0);
int pan=0,sum=0;
for(int i=0;i<=11;i++)
{
cin>>out[i];
}
for(int i=0;i<=11;i++)
{
int sheng=shou[i]-out[i];
if(sheng>0)
{
sum+=sheng/100*100;
shou[i+1]+=sheng%100;
};
if(sheng<0){
pan=i+1;
break;
}
}
if(pan)
{
cout<<-pan;
return 0;
}
else
{
cout<<sum*1.2+shou[12]-300;
return 0;
}
}
刚刚摸鱼看了一些C语言趣味故事,才知道左花括号的位置还区分出了两种代码风格,然后发现自己一直写的是K&R风格,故在此尝试微软风格,对称的花括号看起来果然更舒服了(^▽^)
总结
题目较复杂时,尝试在草稿纸上模拟过程,寻找关系,不要一直发呆(ーー;)
洛谷P1035
题目描述
已知:Sn=1+21+31+…+n1。显然对于任意一个整数 k,当 n 足够大的时候,Sn>k。
现给出一个整数 k,要求计算出一个最小的 n,使得 Sn>k。
输入格式
一个正整数 k。
输出格式
一个正整数 n。
解题过程
写完这题更是坚定了我要买《C Primer Plus》的决心,WA的原因全是因为小语法毛病……
很简单的题目,用模拟尝试到满足条件即可:
//模拟
#include<iostream>
using namespace std;
int main()
{
int n=1,k;
double sum=0;//当k较大时,flout精度不够
cin>>k;
while((sum+=1.0/n)<=k)//注意用1.0隐性转换
{
n++;
}
// for(double sum=0;(sum+=1.0/n)<=k;n++);另一种循环方式
cout<<n;
return 0;
}
美美AC后在题解区发现了更强的数论做法,故在理解后记录:
//数论
#include<iostream>
#include<cmath>
using namespace std;
int main()
{
double gamma=0.5772156649;
int k,n;
cin>>k;
n=exp(k-gamma)+0.5;//exp函数用于求自然底数e的次方,括号内可接受整形或浮点型数据
cout<<n;
return 0;
}
n的式子由调和级数求和公式推导得来,虽然还是有很多疑问比如在C语言截断式取整的情况下为什么+0.5就能不出错balabala,略得出调和级数求和公式存在误差的结论,不深究了反正该方法就当拓展吧。
总结
除了暴力模拟,可以尝试思考题目中的数学原理,用数学知识优化代码。
(不会告诉你本人为此已花两小时研究数论且一无所获)
洛谷P1980
题目描述
试计算在区间 1 到 n 的所有整数中,数字 x(0≤x≤9)共出现了多少次?例如,在 1 到 11 中,即在 1,2,3,4,5,6,7,8,9,10,11 中,数字 1 出现了 4 次。
输入格式
2 个整数 n,x,之间用一个空格隔开。
输出格式
1 个整数,表示 x 出现的次数。
解题过程
第一反应是用取模取整运算分离数字各个数位:
#include<iostream>
using namespace std;
int main()
{
int n=0,x=0,k=0;
cin>>n>>x;
for(int i=1;i<=n;i++)
{
int m=i;
while(m>0)
{
if(m%10==x)
{
k++;
}
m=m/10;
}
}
cout<<k;
return 0;
}
不出意外的话又出意外了,我的第一版代码在循坏内部直接对 i 进行处理导致其值改变,影响循环条件,直接TLE了,已对自己无奈ヽ(ー_ー)ノ
然后学了一些string容器函数,有了以下写法:
#include<iostream>
#include<cmath>
#include<sstream>
#include<algorithm>
using namespace std;
int main()
{
int n=0,x=0,k=0;
cin>>n>>x;
for(int i=1;i<=n;i++)
{
string s=to_string(i);//int转换为string
for(int j=0;j<s.size();j++)
{
if(s[j]==x+'0')//s[j]为字符,应将x转换为字符再比较
{
k++;
}
}
}
cout<<k;
}
怎么感觉还是很复杂,于是又找到了个函数:count
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
int n=0,x=0,k=0;
string s;
cin>>n>>x;
for(int i=1;i<=n;s+=to_string(i),i++);
k=count(s.begin(),s.end(),x+'0');
cout<<k;
return 0;
}
头文件kuku引,代码kuku改,怎么看起来头重脚轻的[・_・?]
总结
灵活选择使用适合的C++函数,可以极大的简化代码,达到效果。
洛谷P1014
题目描述
现代数学的著名证明之一是 Georg Cantor 证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的:
我们以 Z 字形给上表的每一项编号。第一项是 1/1,然后是 1/2,2/1,3/1,2/2,…
输入格式
整数N(1≤N≤107)。
输出格式
表中的第 N 项。
解题过程
看着这别扭的排布,灵机一动,脖子一歪,看成斜着的一行行数字,观察到行数、项数与分子分母之间的关系,故有第一版代码:
#include<iostream>
using namespace std;
int main()
{
int zi=1,mu=1;
int n,hang=1;
cin>>n;
while((n-hang)>0)
{
n=n-hang;
hang++;
}
if(hang%2==0)
{
mu=hang;
mu=mu-(n-1);
zi=zi+(n-1);
}
else
{
zi=hang;
zi=zi-(n-1);
mu=mu+(n-1);
}
cout<<zi<<'/'<<mu;
}
感觉变量太多了,重复的部分也很多,似乎可以用n和hang代替zi和mu,尝试列关系式替换来化简,于是得到了非常简洁的第二版:
#include<iostream>
using namespace std;
int main()
{
int n,hang=1;
cin>>n;
for(;n-hang>0;n-=hang,hang++);
if(hang%2==1)
cout<<hang-(n-1)<<'/'<<n;
else
cout<<n<<'/'<<hang-(n-1);
}
总结
寻求变量间的关系,利用关系式可以替换化简多余的部分。
洛谷P1014
题目描述
给定一个整数 N,请将该数各个位上数字反转得到一个新数。新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零(参见样例 2)。
输入格式
一个整数 N。
输出格式
一个整数,表示反转后的新数。
解题过程
刚开始思考半天字符串的解法,又要反转又要消0,最后还是用取模取整运算写了(参考了题解)
#include<bits/stdc++.h>
using namespace std;
int n,s=0;
int main()
{
cin>>n;
while(n) s=s*10+n%10,n/=10;
cout<<s;
return 0;
}
看完之后真的直呼精彩,六道题里第一个这么让我哇塞的,妙啊妙啊喵啊~
可以思考一下为什么正负数情况通用,且不用单独写代码消0
总结
没有总结,多欣赏简洁的代码对身心健康有好处
END
看似一篇日记,实际写了两天,六道题让自己无比稀碎的语言基础暴露无遗(T_T)。这是我的博客第一篇与